2013年7月,漏洞Struts2的漏洞S2-016漏洞余波还没散干净,Apache官方又甩出一个补丁,漏洞结果补丁本身成了下一个灾难的漏洞起点。这就是漏洞S2-052——一个典型的"修一个坑,挖一个更大的漏洞坑"的故事。
先说清楚它到底怎么回事。漏洞Struts2的漏洞REST插件在反序列化XML请求体时,用的漏洞是XStream库。XStream能把XML直接映射成Java对象,漏洞方便是漏洞真方便,但它在处理某些特定类型的漏洞对象时,会触发意料之外的漏洞代码执行。攻击者只需要往REST端点发一段精心构造的漏洞XML,就能在服务器上跑任意命令。漏洞不需要认证,不需要复杂的前置条件,一个POST请求搞定。
这东西危险在哪?Struts2本身在企业Java Web里用得太广了。政府、银行、电信,大量老系统跑在上面。REST插件虽然不是每个项目都用,但用了的,基本就是把门敞开着。更麻烦的是,很多运维根本不知道自己的应用有没有启用REST插件——它可能藏在某个依赖包里,安安静静地等着被人发现。
PoC出来的速度很快。漏洞公开后不到一周,GitHub上就能搜到各种利用工具。我印象很深的是一个叫marshalsec的项目,本来是研究Java反序列化问题的,结果被直接拿来打S2-052。那段XML payload长得不算复杂,核心就是通过XStream的漏洞链加载远程类。当时圈子里有人开玩笑说,"这玩意儿比写个Hello World难不了多少"。
Apache的反应倒是不慢,9月5日发公告,9月7日出了2.5.13版本修复。但问题在于,Struts2的版本碎片化太严重了。2.3.x和2.5.x两条线并行维护,很多项目还停留在2.3.x上,而修复只针对2.5.13。换句话说,用老版本的人,要么升级到2.5.x(可能引入兼容性问题),要么自己想办法打补丁。现实是,大部分团队两个都没做。
我后来跟一个做渗透测试的朋友聊过这事。他说2018年的时候,扫一个省的教育系统,还能在几十台服务器上打到S2-052。不是没人管,是管不过来。那些系统可能三五年没人动过,开发早离职了,运维只管机器别宕机。漏洞?排期?不存在的。
从技术角度看,S2-052暴露的问题比漏洞本身更值得琢磨。XStream这种库的设计哲学是"信任输入",默认什么类都能反序列化。这在封闭环境里没问题,但一旦暴露在公网,就是灾难。Java生态里类似的库不少,Fastjson、Jackson-databind都栽过同样的跟头。说白了,反序列化这个操作,天生就不该信任任何外部数据。
修复建议其实就两条:升级到2.5.13或更高版本,或者干脆把REST插件禁掉。如果业务不需要XML格式的REST请求,在struts.xml里把相关action的parse参数关掉就行。但说实话,大部分中招的项目,连自己用了REST插件都不知道,谈何配置。
这事过去快十年了。S2-052在CVE里躺得好好的,CVSS评分9.8,严重级别。每年护网行动,它都在漏洞扫描器的规则库里。新系统很少再用Struts2了,但老系统还在跑。那些系统就像城市地下的老旧管道,你知道它有问题,但换起来太贵,只能等它哪天真的爆了再说。
安全圈有句话:漏洞不会消失,只会转移。S2-052是Struts2系列里比较典型的一个,但它不是第一个,也不会是最后一个。只要还有系统在用十年前的技术栈,这类故事就会一直重复。