基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复
2024-7-16 15:8:44 Author: www.freebuf.com(查看原文) 阅读量:1 收藏

前置知识:

Java动态代理

静态代理

假设现在有这么一个需求:

  • 创建了一个接口A,里面有display()函数、select()函数、add()函数,类AImpl实现了这三个函数,类AstaticProxy作为类AImpl的日志类,在AImpl每个函数执行完打印“调用了display函数”,调用了"select函数"...类似对AImpl进行装饰

即如下实现代码:

//接口Ainterface
public interface Ainterface {
    public void display();
    public void select();
    public void add();
}
//类AImple
public class AImpl implements Ainterface{
    public void display()
    {
        System.out.println("display");
    }
    public void select(){
        System.out.println("select");
    }
    public void add()
    {
        System.out.println("add");
    }
}
//AImpl的日志类AstaticProxy,在AImpl实例上添加功能
public class AstaticProxy implements Ainterface{
    private Ainterface aimpl;
    public AstaticProxy(Ainterface a){
        this.aimpl = a;
    }
    public void display()
    {
        aimpl.display();
        System.out.println("调用了display");
    }
    public void select(){
        aimpl.select();
        System.out.println("调用了select");
    }
    public void add()
    {
        aimpl.add();
        System.out.println("调用了add");
    }
}

以上就完成了一个静态代理

在主函数中进行测试:

public class AProxyTest {
    public static void main(String[] args) throws Exception
    {
        Ainterface a = new AImpl();
        a.add();
        a.display();
        a.select();
        Ainterface aproxy = new AstaticProxy(a);
        aproxy.add();
        aproxy.display();
        aproxy.select();
    }
}

image-20240711220409517

这样代理类AstaticProxy中进行的行为是AImpl多余的,不会影响原本的AImpl类,这就叫静态代理。

你也观察到了,三个方法打印,就要写三遍,而且是高度重复的代码,又或者是要在每个函数前面加同一个自定义函数。如果类似Map接口,方法多到离谱,也要冗余的写十几遍吗?此时就有了动态代理。

动态代理

java.lang.reflect包下的Proxy类和InvocationHandler接口组合使用就能创建一个动态代理实例

现在让我们舍弃AstaticProxy类,尝试写AdynamicProxy的动态代理类

这个代理类需要实现InvocationHandler接口,该接口内只有一个方法需要实现,就是invoke

image-20240711222419213

在invoke内重写我们需要在每个方法内添加的内容。

在这里我们先不用关心怎么调用这个invoke(也就是如何传参),只需要知道在invoke内怎么使用这三个参数,proxy指代理对象本身,即new AdynamicProxy生成的对象,method是调用的方法,如add();args是调用方法传的参数,如add()无参方法就是null。

//动态代理类AdynamicProxy,代理实现了Ainterface的类
public class AdynamicProxy implements InvocationHandler {
    private Ainterface a;

    public AdynamicProxy(Ainterface a)
    {
        this.a = a;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        Object result = method.invoke(a, args);
        String methodName = method.getName();
        System.out.println("调用了"+methodName);
        return result;
    }
}

使用这个动态代理类,用Proxy.newProxyInstance生成代理类对象,看这个函数需要的参数:

第一个参数需要一个ClassLoader,通用写法.class.getClassLoader(),第二个参数传接口数组,可以写new Class[]{Ainterface.class},也可以用通用写法a.getClass().getInterfaces(),第三个参数传实现了InvocationHandler的代理类,这样就生成了代理类实例。

**调用代理类实例的方法,会执行代理类的invoke方法。**通过反射Object result = method.invoke(a, args);调用了AImpl的对应方法

image-20240713110500610

public class AProxyTest {
    public static void main(String[] args) throws Exception
    {
        Ainterface a = new AImpl();
        Ainterface aproxy = (Ainterface) Proxy.newProxyInstance(Ainterface.class.getClassLoader(), new Class[]{Ainterface.class}, new AdynamicProxy(a));
        aproxy.add();
        aproxy.display();
    }
}

image-20240713110426076

且我们看到Proxyk可以序列化。

image-20240714162202753

newProxyInstance背后的逻辑

我们分析一下newProxyInstance怎么创建的动态代理实例,我们的动态代理类AdynamicProxy里面只有一个构造函数和invoke方法,当然不能直接new生成。这个动态代理实例实际的装配过程就在newProxyInstance。

该函数内大多数代码都是安全检查和获取访问权限,重点在以下三句。最重要的就是getProxyClass0方法。

image-20240713112129827

getProxyClass0方法内,调用了get方法查找缓存内有无已生成的代理类

image-20240713112555131

这里proxyClassCache是一个WeakCache,WeakCache的get方法如果没有查找到对应键值,会创建一个新的条目,具体创建细节此处省略。

image-20240713114024588

image-20240713113953632

ProxyClassCache的键是对接口的哈希,如调用的Key1方法,值是ProxyClassFactory工厂类生成的类

image-20240713114408167

在ProxyClassFactory内就生成了代理实例的类名

image-20240713114549185

image-20240713152350740

ProxyGenerator.generateProxyClass生成代理实例的字节码,defineClass0加载字节码

image-20240713114644105

该类下调用的generateClassFile()

image-20240713114843464

该方法遍历向每个方法中添加了generateMethod()方法,而generateMethod则是生成后的invoke内的代码,到这里就结束分析啦

image-20240713115049701

image-20240713115155767

所以代理类的实现是重新生成了一个代理对象的class文件,该文件内依此向每个方法添加invoke的内容,最后defineClass加载字节码。让我们来找找这个class文件,验证一下分析。

代理对象文件分析

上文介绍了ProxyGenerator.generateProxyClass()方法生成了代理类的字节码文件,我们将这个虚拟机中的文件输出出来。

image-20240713140425820

我们调用简化版的generateProxyClass,随便取个名字,传入a.getClass().getInterface(),输出文件

image-20240713144645805

public class AProxyTest {
    public static void main(String[] args) throws Exception
    {
        Ainterface a = new AImpl();
        byte[] classFile = ProxyGenerator.generateProxyClass("org.example.AProxyExtract", a.getClass().getInterfaces());
        try(FileOutputS

文章来源: https://www.freebuf.com/vuls/406123.html
如有侵权请联系:admin#unsafe.sh