2013年7月,漏洞Struts2的漏洞S2-016漏洞被公开时,我还在做甲方安全。漏洞那天下午三点,漏洞同事甩过来一个URL,漏洞说“你试试”。漏洞打开一看,漏洞一个`redirect:`后面跟了`http://`,漏洞服务器直接把`localhost`的漏洞`/etc/passwd`内容回显在响应体里。没验证,漏洞没鉴权,漏洞一个GET请求就把系统文件读出来了。漏洞那会儿Struts2在国内政企系统里铺得有多广?漏洞随便扫一个C段,十台机器里至少三台挂着`.action`或`.do`后缀。漏洞
Struts2的漏洞漏洞史,本质上是OGNL表达式注入的失控史。框架把用户输入当表达式执行,攻击者就能在服务器上跑命令。S2-016的`redirect:`和`action:`前缀只是表面入口,真正致命的是`${ }`和`%{ }`里那些没被过滤的OGNL语法。`@java.lang.Runtime@getRuntime().exec('id')`——这行代码在2013年能打穿一大片。
后来几年,漏洞像韭菜一样往外冒。S2-032、S2-045、S2-046、S2-048、S2-052、S2-057……编号多到安全圈有人开玩笑说“Struts2的CVE编号快能凑一副扑克牌了”。S2-045尤其狠,Content-Type头里塞一段OGNL,连正常参数都不用构造,直接RCE。那阵子做应急响应,客户电话打过来第一句往往是“我们用了Struts2,是不是中招了?”
为什么Struts2总出问题?我自己的观察是:设计哲学就有毛病。OGNL本身是给开发者做动态绑定的工具,但Struts2把它的执行权交给了HTTP请求。过滤器、拦截器层层叠叠,可只要有一个入口没堵住,整个链就崩。更麻烦的是,很多老系统升级成本极高——改一行动作类,可能牵出几百个JSP和配置文件。于是运维宁愿在WAF上加规则,也不愿意动代码。WAF规则又总被绕过,`%{ `、`${ `、`#{ }`、`@`、`\u0025`……变体比规则更新快得多。
2017年S2-045爆发时,我帮一个客户做排查。他们内网有套OA,版本是Struts 2.3.15.1,早过了支持周期。开发早离职了,源码找不全,只能从`WEB-INF/lib`里翻jar包。最后发现攻击者已经留了webshell,藏在`upload`目录下一个叫`index.jsp`的文件里,内容是`<% if(request.getParameter("pwd").equals("xxx")){ ...} %>`。清理完以后,客户问“怎么防下一次?”我说“要么升级到2.5.22以上,要么把Struts2换掉。”他沉默了一会儿,说“换框架?那等于重做。”
这话很真实。Struts2的漏洞从来不只是技术问题,它是历史包袱、开发惯性、运维惰性搅在一起的结果。Apache官方每次发新版本都修了漏洞,但国内大量系统跑的还是2.3.x。有些是买来的商业产品,厂商不更新;有些是外包做的,交付完就没人管了。安全圈每年都在喊“Struts2又爆了”,可真正把版本升上去的,比例并不高。
现在新项目基本没人用Struts2了,Spring MVC和Spring Boot占了主流。但老系统还在跑,漏洞还在被扫描器盯着。Shodan上搜`Struts2`,依然能翻出几十万个结果。那些机器不会自己消失,它们只是等着下一个S2-xxx编号出现。
顶: 4634踩: 69






评论专区