个人学习笔记,有些地方可能没做记录,或者记录在别处,仅供参考
Java反射
几个常用的方法
- 获取类的⽅方法: forName
- 实例例化类对象的⽅方法: newInstance
- 获取函数的⽅方法: getMethocd
- 执⾏行行函数的⽅方法: invoke
反射赋值
利用反射给name和age字段赋值:
import java.lang.reflect.Field;
public class anl { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
String name = "Xiao Ming"; int age = 20; Person1 p = new Person1(); Class<? extends Person1> c = p.getClass(); Field f1 = c.getDeclaredField("name"); Field f2 = c.getDeclaredField("age"); f1.setAccessible(true); f1.set(p, name); f2.setAccessible(true); f2.set(p, age);
System.out.println(p.getName()); System.out.println(p.getAge()); } }
class Person1 {
private String name; private int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; } }
|
初始化
在p牛Java安全漫谈里讲到了一个初始化
还有这位师傅的文章
public class TrainPrint { { System.out.printf("Empty block initial %s\n", this.getClass()); } static { System.out.printf("Static initial %s\n", TrainPrint.class); } public TrainPrint() { System.out.printf("Initial %s\n", this.getClass()); } }
|
用Class.forName
来输出一下
public class TrainPrint { public static void main(String[] args) throws ClassNotFoundException { Class.forName("TrainPrint"); } { System.out.printf("Empty block initial %s\n", this.getClass()); } static { System.out.printf("Static initial %s\n", TrainPrint.class); } public TrainPrint() { System.out.printf("Initial %s\n", this.getClass()); } }
|
用new
关键字来实例化输出
public class TrainPrint { public static void main(String[] args) throws ClassNotFoundException { TrainPrint trainPrint = new TrainPrint(); } { System.out.printf("Empty block initial %s\n", this.getClass()); } static { System.out.printf("Static initial %s\n", TrainPrint.class); } public TrainPrint() { System.out.printf("Initial %s\n", this.getClass()); } }
|
这样我们可以知道这三种”初始化”方法的调用顺序
类的实例化:static {}
->{}
->构造函数
类的初始化:static {}
并且,Class.forName
中的initialize=true
实际上是告诉java虚拟机执行类的初始化,而不是实例化,这个区别要注意。
反射执行命令
$
的作用是查找内部类
Java的普通类 C1 中支持编写内部类 C2 ,而在编译的时候,会生成两个文件: C1.class 和 C1$C2.class ,我们可以把他们看作两个无关的类,通过 Class.forName(“C1$C2”) 即可加载这个内部类。
获得类以后,我们可以继续使用反射来获取这个类中的属性、方法,也可以实例化这个类,并调用方法。
|
其中class.newInstance()
就是可以调用类中的无参构造方法,而遇到下面两种情况,会发现利用总是不成功
1.使用的类没有无参构造方法
2.使用的类构造方法是私有的
最常见的就是Runtime
类的构造方法是私有的
Class<?> cls = Class.forName("java.lang.Runtime"); cls.getMethod("exec", String.class).invoke(cls.newInstance(), "id");
|
所以我们这样执行命令是不成功的,会报错
这涉及到设计模式中的单例模式,只有在类初始化的时候执行一次构造方法,然后只能通过getRuntime
来获取这个对象
于是我们修改一下payload就可以执行
public class Arrd2 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { Class<?> cls = Class.forName("java.lang.Runtime"); cls.getMethod("exec", String.class).invoke(cls.getMethod("getRuntime").invoke(cls), "curl 127.0.0.1:8000"); } }
|
将上述payload分解一下,更容易理解
Class<?> cls = Class.forName("java.lang.Runtime"); Method execMethod = cls.getMethod("exec", String.class); Method getRuntimeMethod = cls.getMethod("getRuntime"); Object runtime = getRuntimeMethod.invoke(cls); execMethod.invoke(runtime, "curl 127.0.0.1:8000");
|
import java.io.IOException;
public class Arrd2 { public static void main(String[] args) throws IOException, InterruptedException { Runtime rt = Runtime.getRuntime(); String[] commands = {"open", "/System/Applications/Calculator.app"}; Process pc = rt.exec(commands); pc.waitFor(); } }
|
import java.io.IOException;
public class Arrd2 { public static void main(String[] args) throws IOException { Runtime.getRuntime().exec("open /System/Applications/Calculator.app"); } }
|
Java代理
Java有三种代理模式静态代理、动态代理和cglib代理
静态代理
优点:
可以达到功能增强的目的,实现简单且不侵入源代码
缺点:
当需要代理多个类时,由于代理对象要实现与目标对象一致的接口,导致代理类过于庞大,繁多。
当接口需要增删改方法时,代理类与目标对象都要修改,不易维护
代理类和目标统一接口
public interface Hello { public void sayHello(String name); }
|
实现类
public class Start implements Hello{
@Override public void sayHello(String name) { System.out.println(name + " said: \"hello everyone\""); } }
|
创建静态代理类,实现接口,并且拓展接口方法sayHello
public class DyProxy implements Hello{ private Hello target; public DyProxy(Hello obj) { this.target = obj; } @Override public void sayHello(String name) { System.out.println("我是代理"); this.target.sayHello(name); } }
|
测试类
public class App { public static void main(String[] args) { Hello hello = new Start(); Hello helloProxy = new DyProxy(hello); helloProxy.sayHello("qqw"); } }
|
结果
通过静态代理,我们实现了扩展接口方法,而且没有侵入源代码
动态代理
两者区别
- 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
- 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
接口和实现类不变,我们再创建一个动态代理类
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;
public class DyProxy{ private Hello target; public DyProxy(Hello obj) { this.target = obj; } public Object getProxyInstance() { return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method); System.out.println("我是代理"); method.invoke(target, args); return null; } }); } }
|
可以发现,动态代理不需要实现接口,但必须要求目标对象要实现接口,因为:
new ProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler )
|
ProxyInstance
方法第二个参数需要目标对象实现的接口
第三个参数InvocationHandler
,必须要实现invoke
方法
测试类
public class App { public static void main(String[] args) { Hello hello = new Start(); DyProxy dyProxy = new DyProxy(hello); Hello helloProxy2 = (Hello) dyProxy.getProxyInstance(); System.out.println(helloProxy2.getClass()); helloProxy2.sayHello("qqw"); } }
|
参考
https://github.com/Maskhe/javasec/blob/master/java%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86.md
https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984#0
https://mp.weixin.qq.com/s?__biz=MzI1NDU0MTE1NA==&mid=2247483792&idx=1&sn=3235b732de5773c982726c3d0bbbe66c&chksm=e9c2ed9ddeb5648b4529c5f60a8629175e1a62e65d91968c58a56a69bb77a18e1968e2633f87&scene=0#rd