Struts2 框架在 Java Web 开发历史上曾占据举足轻重的地位,但其频繁爆出的高危漏洞也让无数运维和开发团队夜不能寐。即便在今天,大量存量的老系统依然运行在 Struts2 环境下,彻底重构成本高昂,因此针对历史漏洞的精准修复与安全升级成为了最务实的防护手段。这不是一个简单的替换 Jar 包的过程,而是涉及兼容性测试、配置修改、语法变更以及防护策略叠加的系统工程。
核心高危漏洞的具体修复手段S2-045 和 S2-046 是基于 Jakarta Multipart 解析器的经典远程代码执行漏洞。修复这两个漏洞不能仅仅依靠黑名单过滤,最根本的方案是升级 Struts2 版本至 2.3.32 或 2.5.10.1 以上。如果系统无法立即升级,必须确认 Commons FileUpload 库已更新至 1.3.3 以上版本,并强制修改配置,将默认的 Jakarta 解析器替换为 Pell 解析器。在 struts.xml 中增加常量配置:
<constant name="struts.multipart.parser" value="pell" />
S2-057 漏洞源于对 namespace 和 result type 的处理逻辑缺陷。修复时,除了升级核心包,必须严格审查 struts.xml 中所有 Result 配置。禁止在 location 参数中使用 ${} 表达式引用用户可控的输入。如果业务逻辑确实需要动态跳转,必须在 Action 层做严格的输入校验,限制传入值只能是预定义的常量集合,绝不能直接拼接 URL 路径。
S2-059 和 S2-061 是 OGNL 注入的变种。针对这类漏洞,单纯升级版本有时不够,因为 OGNL 的沙箱逃逸技术不断进化。硬核的修复方式是在代码层面彻底禁用 OGNL 表达式的双次解析。对于标签属性中确实需要动态赋值的场景,应强制使用 %{ } 语法,并确保 value 栈中的对象不包含敏感的执行链。同时,在 web.xml 中配置严格的安全约束,拦截所有直接访问 .action 后缀且带有可疑表达式参数的请求。
升级前的环境兼容性深度排查从 Struts 2.3 向 2.5 或更高版本迁移时,包名的变更是最容易忽视的致命陷阱;
2.5 版本中,核心包从 xwork-core 变更为 struts2-core,xwork 相关的类库被彻底合并。如果项目中直接引用了 com.opensymphony.xwork2 下的工具类,编译时会直接报错,但在热部署或模块化加载的老系统中,这种冲突往往在运行时才暴露,表现为 NoClassDefFoundError。排查时,必须使用 Maven 的 dependency:tree 命令逐项检查传递依赖,排除所有旧版本的 xwork 残留。
拦截器栈的默认行为发生了重大改变;
2.5 版本引入了更为严格的 MethodFilterInterceptor 机制。老系统中如果自定义了拦截器栈,并且使用了通配符配置方法调用,升级后可能会出现方法找不到或被拒绝访问的情况。必须检查 struts.xml 中所有 interceptor-ref 的配置,将 excludeMethods 和 includeMethods 的列表显式声明,避免依赖默认的模糊匹配逻辑。同时,Dynamic Method Invocation 功能在默认配置中被关闭,如果老系统大量使用 action!method 的 URL 调用方式,需要在配置中显式开启,但这会带来安全风险,更建议改为基于通配符的配置方式。
JSON 插件的序列化机制升级是另一个重灾区。老版本中,JSON 插件会序列化 Action 类中的所有属性,而新版本为了安全,默认只序列化提供了 getter 方法的属性,且对包含复杂对象图的属性做了递归深度限制。这会导致前端接收到的 JSON 数据结构突变,字段丢失。修复时,需要在 Action 类上使用 @JSON 注解精确控制序列化字段,或者在 struts.xml 中配置 includeProperties 正则表达式,显式指定需要输出的属性列表。
Struts2 配置文件的加固策略struts.xml 中的 devMode 常量在生产环境中必须严格设为 false。开发模式下,框架会输出详细的 OGNL 解析错误和堆栈信息,这直接为攻击者提供了敏感的内部路径和代码逻辑。同时,struts.enable.DynamicMethodInvocation 必须设为 false,除非业务有无法修改的强依赖。对于文件上传功能,必须限制最大文件大小和允许的 MIME 类型,在 struts.xml 中配置拦截器参数:
<interceptor-ref name="fileUpload">
<param name="maximumSize">10485760</param>
<param name="allowedTypes">image/png,image/jpeg,application/pdf</param>
</interceptor-ref>
这种配置将上传限制在 10MB 以内,且只允许图片和 PDF 类型,从入口处阻断恶意文件上传。此外,对于不需要文件上传的模块,应彻底移除 fileUpload 拦截器栈,减少攻击面。
OGNL 表达式注入的终极防护是在框架层面提升沙箱强度。在 Struts 2.5.20 之后的版本中,可以通过配置禁止访问特定的类和方法。在 struts.xml 中添加常量:
<constant name="struts.ognl.excludedClasses" value="java.lang.Runtime,java.lang.ProcessBuilder,java.lang.reflect" /> <constant name="struts.ognl.excludedPackageNames" value="java.lang.reflect,javax.script,sun.misc" />
这种黑名单机制虽然不能覆盖所有未知攻击向量,但能有效阻断绝大多数基于反射和命令执行的利用链。同时,应结合白名单思维,在拦截器中校验所有传入参数的键名,拒绝包含 #、% 等 OGNL 特殊字符的参数名。
依赖库的精细化升级与冲突解决Struts2 的安全不仅仅是核心框架的事。Freemarker 模板引擎的版本必须随框架一同升级。老版本 Freemarker 存在模板注入风险,攻击者若能控制模板内容或变量名,同样可以导致远程代码执行。升级 Struts2 至 2.5.30 以上版本时,应确保 Freemarker 版本不低于 2.3.31。在 Maven 的 pom.xml 中,必须显式声明 Freemarker 的版本,防止传递依赖引入低版本:
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
Log4j2 的历史漏洞同样波及 Struts2 生态。很多 Struts2 应用使用了 Log4j2 作为日志框架。在修复 Struts2 漏洞的同时,必须将 Log4j2 升级至 2.17.1 以上版本以规避 JNDI 注入。如果系统因兼容性问题无法升级 Log4j2,至少要在启动参数中增加 -Dlog4j2.formatMsgNoLookups=true,并移除 log4j-core 中的 JndiLookup 类。这可以通过 zip 命令直接删除 Jar 包中的特定 class 文件实现:
zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
OWASP 的依赖检查插件应集成到 CI/CD 流水线中。每次构建时,通过 mvn dependency-check:check 命令生成安全报告,自动阻断包含已知 CVE 漏洞的依赖进入制品库。对于无法升级的遗留依赖,必须在部署时通过 RASP 或 WAF 配置虚拟补丁。
运行时防护与监控的叠加版本升级和配置加固属于静态防御,面对零日漏洞时依然脆弱。在生产环境中,应在 JVM 启动参数中添加 RASP 探针,监控 OGNL 表达式的执行行为。一旦检测到脚本引擎调用、Runtime 执行或类加载器的不正常行为,立即阻断请求并上报告警。同时,在反向代理层配置严格的 URL 白名单。Struts2 应用通常只需要暴露特定的 .action 或 .do 后缀接口,对于直接访问 .jsp 或 WEB-INF 下文件的请求,应在 Nginx 或 Apache 层直接拒绝:
location ~* \.(jsp|class|xml|properties)$ {
deny all;
return 403;
}
location ~* /WEB-INF/ {
deny all;
return 403;
}
日志审计也是关键一环。开启 Struts2 的详细访问日志,记录每个请求的完整 URL、参数和返回状态码。对于参数中包含 %{、${、#_memberAccess 等敏感字符串的请求,即使被框架拦截,也应记录其来源 IP 并触发告警,因为这往往是攻击者在进行漏洞探测。通过分析这些日志,可以在攻击成功前发现并封禁扫描源。
回归测试的完整执行清单升级完成后,全量回归测试是避免业务中断的最后防线。测试重点不应只放在正常功能流程上,边界测试和异常注入测试同等重要。对于文件上传接口,应构造包含畸形文件名、超长 Content-Type、以及文件名中包含 ../ 路径穿越字符的请求,验证拦截器是否生效。对于所有接受参数输入的 Action,应使用自动化扫描器重放常见的 OGNL 注入 Payload,确认响应中不包含命令执行的回显。
性能测试也不可忽视。新版本 Struts2 对拦截器链和 OGNL 解析做了优化,但也可能因为更严格的校验逻辑导致 CPU 占用升高。应在预发布环境中模拟生产流量,对比升级前后的 TPS 和响应时间,重点关注 GC 频率和线程阻塞情况。如果性能下降明显,需要排查是否因为开启了过多的日志输出或正则匹配规则过于复杂。
对于使用了 Struts2 的 REST 插件或 Convention 插件的项目,升级后必须验证所有 URL 映射是否依然正确。Convention 插件在新版本中对类名到 URL 的映射规则进行了微调,可能导致部分接口 404。此时需要检查所有 Action 类的命名是否符合新规则,或者通过 @Action 注解显式指定路径,覆盖默认的约定映射。
