JAVA反射和代理

个人学习笔记,有些地方可能没做记录,或者记录在别处,仅供参考

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()); // "Xiao Ming"
System.out.println(p.getAge()); // 20
}
}

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 {
// Class.forName("TrainPrint"); //类的初始化
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

作者

秋秋晚

发布于

2022-09-07

更新于

2023-01-10

许可协议

评论

:D 一言句子获取中...