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()的封装。
参看:
/*
* 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中只有必要代码,可以避免初看者失焦。