标题: 从RMI Registry中转储动态端口信息 创建: 2020-04-23 20:47 更新: 链接: https://scz.617.cn/network/202004232047.txt -------------------------------------------------------------------------- 目录: ☆ 前言 ☆ 从RMI Registry中转储动态端口信息 1) rmiinfo.java 2) rmi-dumpregistry.nse 3) rmiregistry_detect.nasl 4) jndiinfo.java 4.1) jndiinfo.policy -------------------------------------------------------------------------- ☆ 前言 本篇内容已全部补充到下文中: 《Java RMI入门》 https://scz.617.cn/network/202002221000.txt 该系列已经写到(9),都快写成小书了。除非特别有兴趣者,甚少有人会回头去看更 新、增补内容,所以开个单章,以免有人错失一些潜在有用的信息。至于这篇: 《Java反序列化利用中绕过Registry白名单检查》 https://scz.617.cn/network/202004211620.txt 个人觉得不算入门系列中的内容,我也是从这篇开始觉得自己在Java RMI方面略有进 阶。现在若是有人拿Java RMI的攻防机关来问我,不至于听不懂对方在问什么。 ☆ 从RMI Registry中转储动态端口信息 ONC/Sun RPC有个rpcinfo,可以列出向rpcbind注册过的所有动态端口。Java RMI没 有官方工具完成类似任务,只有第三方工具或自己编程实现。 1) rmiinfo.java -------------------------------------------------------------------------- /* * javac -encoding GBK -g rmiinfo.java * java rmiinfo 192.168.65.23 1099 */ import java.rmi.registry.*; public class rmiinfo { public static void main ( String[] argv ) throws Exception { String addr = argv[0]; int port = Integer.parseInt( argv[1] ); Registry r = LocateRegistry.getRegistry( addr, port ); String[] names = r.list(); // for ( int i = 0; i < names.length; i++ ) // { // System.out.println( names[i] ); // } for ( String name : names ) { System.out.println( name ); } } } -------------------------------------------------------------------------- 这是最简单也最无用的工具,只能从RMI Registry中转储所有绑定过的name。 2) rmi-dumpregistry.nse nmap提供了一个脚本: https://nmap.org/nsedoc/scripts/rmi-dumpregistry.html https://svn.nmap.org/nmap/scripts/rmi-dumpregistry.nse $ nmap -n -Pn -p 1099 --script rmi-dumpregistry.nse 192.168.65.23 PORT STATE SERVICE 1099/tcp open java-rmi | rmi-dumpregistry: | any | implements java.rmi.Remote, HelloRMIInterface, | extends | java.lang.reflect.Proxy | fields | Ljava/lang/reflect/InvocationHandler; h | java.rmi.server.RemoteObjectInvocationHandler | @192.168.65.23:42524 | extends |_ java.rmi.server.RemoteObject nmap的"-sC"或"--script=default"默认会调用上述脚本。 3) rmiregistry_detect.nasl Nessus提供了一个插件,可以从RMI周知端口转储那些向之注册过的动态端口信息。 $ nasl -t 192.168.65.23 rmiregistry_detect.nasl Valid response recieved for port 1099: 0x00: 51 AC ED 00 05 77 0F 01 A6 D3 99 DA 00 00 01 71 Q....w.........q 0x10: A7 28 23 89 80 05 75 72 00 13 5B 4C 6A 61 76 61 .(#...ur..[Ljava 0x20: 2E 6C 61 6E 67 2E 53 74 72 69 6E 67 3B AD D2 56 .lang.String;..V 0x30: E7 E9 1D 7B 47 02 00 00 70 78 70 00 00 00 01 74 ...{G...pxp....t 0x40: 00 03 61 6E 79 ..any Here is a list of objects the remote RMI registry is currently aware of : rmi://192.168.65.23:42524/any rmiregistry_detect.nasl的解码能力比nmap脚本rmi-dumpregistry.nse差远了,但 最关键的name与动态端口之间的映射关系被解码后显示出来。 单扫rmiregistry_detect.nasl有点慢,可以自己精简一下插件。此外,NASL语法直 白,很容易改写成Python版本。 4) jndiinfo.java 之前以为必须进行socket编程才能从RMI Registry中转储动态端口信息,后来意识到 lookup()返回的其实是RemoteObject在本地的代理,其中必有ref信息。先list(), 再对每个name依次lookup(),然后输出返回的Object的字符串形式。后来发现另一批 API: com.sun.jndi.rmi.registry.RegistryContext.listBindings() com.sun.jndi.rmi.registry.BindingEnumeration.hasMore() com.sun.jndi.rmi.registry.BindingEnumeration.next() 其实就是list()、lookup()的封装。 参看: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/com/sun/jndi/rmi/registry/RegistryContext.java -------------------------------------------------------------------------- /* * javac -encoding GBK -g jndiinfo.java */ import javax.naming.*; public class jndiinfo { private static void addspace ( StringBuilder sb, int indent, int spacenum ) { for ( int i = 0; i < indent * spacenum; i++ ) { sb.append( ' ' ); } } private static String PrivateFormat ( String sth, int spacenum ) { if ( null == sth || "".equals( sth ) ) { return ""; } StringBuilder sb = new StringBuilder(); char cur; int indent = 0; for ( int i = 0; i < sth.length(); i++ ) { cur = sth.charAt( i ); switch ( cur ) { case '{' : case '[' : sb.append( '\n' ); addspace( sb, indent, spacenum ); sb.append( cur ); sb.append( '\n' ); indent++; addspace( sb, indent, spacenum ); break; case '}' : case ']' : sb.append( '\n' ); indent--; addspace( sb, indent, spacenum ); sb.append( cur ); break; case ',' : sb.append( cur ); sb.append( '\n' ); addspace( sb, indent, spacenum ); break; case ' ' : /* * 相当于删除所有空格 */ break; default : sb.append( cur ); } } return sb.toString(); } /* * 这段代码将自身置于极其危险的境地,对不明远程目标使用时至少要在JVM参 * 数中启用SecurityManager,勿谓言之不预。 */ public static void main ( String[] argv ) throws Exception { /* * 保持一般性,使用JNDI,用JVM参数传递env */ Context ctx = new InitialContext(); /* * 形参只能传空串 */ NamingEnumeration bindings = ctx.listBindings( "" ); while ( bindings.hasMore() ) { Binding bd = ( Binding )bindings.next(); System.out.println ( String.format ( "%s - %s - %s", bd.getName(), bd.getClassName(), PrivateFormat ( bd.getObject().toString(), 4 ) ) ); } ctx.close(); } } -------------------------------------------------------------------------- java \ -Djava.naming.factory.initial=com.sun.jndi.rmi.registry.RegistryContextFactory \ -Djava.naming.provider.url=rmi://192.168.65.23:1099 \ jndiinfo 输出形如: any - com.sun.proxy.$Proxy0 - Proxy [ HelloRMIInterface, RemoteObjectInvocationHandler [ UnicastRef2 [ liveRef: [ endpoint: [ 192.168.65.23:42524 ](remote), objID: [ 3e2be595:171a728632c:-7fff, 1669433444583981789 ] ] ] ] ] 保持一般性,使用JNDI,用JVM参数传递env。这样理论上或可用于其他周知端口,比 如侦听1050/TCP的orbd: java \ -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory \ -Djava.naming.provider.url=iiop://192.168.65.23:1050 \ jndiinfo 输出形如: any - _HelloRMIInterface_Stub - IOR:... JNDI封装未对IOR后面的数据解码显示,直接显示16进制字节流,这种场景没啥大用。 还可以试试: java \ -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory \ -Djava.naming.provider.url=ldap://192.168.65.23:10389/o=anything,dc=evil,dc=com \ jndiinfo 输出形如: cn=any - HelloRMIInterfaceImpl - HelloRMIInterfaceImpl [ UnicastServerRef [ liveRef: [ endpoint: [ 127.0.0.1:36388 ](local), objID: [ 793e717a:171a755a9fc:-7fff, 6413047511569805657 ] ] ] ] 下面这种我没试过,感兴趣者可以自己试: java \ -cp "wlthint3client.jar:." \ -Djava.naming.factory.initial=weblogic.jndi.WLInitialContextFactory \ -Djava.naming.provider.url=t3://192.168.65.23:7001 \ jndiinfo 4.1) jndiinfo.policy rmiinfo.java、jndiinfo.java很容易受到恶意服务端的攻击。对不明远程目标使用 时建议在客户端使用虚拟机,至少要在JVM参数中启用SecurityManager。虽然客户端 CLASSPATH中不一定存在Gadget链的依赖库,但你不知道有什么0day在远端等着你。 不要只想着搞人,总有被反搞的一天。干咱们这行,在这个恶意满满的时代,未谋胜 先谋败。 -------------------------------------------------------------------------- grant { permission java.net.SocketPermission "192.168.65.23:1099", "connect,resolve"; }; -------------------------------------------------------------------------- java \ -Djava.security.manager \ -Djava.security.policy=jndiinfo.policy \ -Djava.naming.factory.initial=com.sun.jndi.rmi.registry.RegistryContextFactory \ -Djava.naming.provider.url=rmi://192.168.65.23:1099 \ jndiinfo 就这种PoC而言,不太喜欢在在代码中启用SecurityManager。jndiinfo.java中只有 必要代码,可以避免初看者失焦。