基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复

2024-07-20 261 0

前置知识:

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();
    }
}

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图

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

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

动态代理

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

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

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

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图1

在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的对应方法

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图2

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();
    }
}

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图3

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

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图4

newProxyInstance背后的逻辑

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

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

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图5

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

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图6

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

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图7

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图8

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

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图9

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

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图10

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图11

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

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图12

该类下调用的generateClassFile()

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图13

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

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图14

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图15

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

代理对象文件分析

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

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图16

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

基于动态代理构造的LazyMap版 CC及利用二次反序列化的修复插图17

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

4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

办事处网络安全监控与事件响应;国外员工终端安全性怎么保障 | FB甲方群话题讨论
拿不下总统之位,那就用热加载拿下验证码识别与爆破好了!
Sooty:一款SoC分析一体化与自动化CLI工具
shiro CVE-2016-6802 路径绕过(越权)
Apache Solr 身份验证绕过漏洞(CVE-2024-45216)详解
llama_index的CVE-2024-4181漏洞根因分析

发布评论