游客发表
2013年7月,远程Struts2的代码S2-016漏洞被公开那天,我正好在给一个客户做渗透测试。执行目标站点的漏洞登录框后面跑的就是Struts2,版本号2.3.15。远程我往URL里塞了一段OGNL表达式,代码服务器直接把执行结果回显在了页面上——那个瞬间的执行感觉,就像你随手拧了一下门把手,漏洞整扇门直接掉下来了。远程
Struts2的代码远程代码执行漏洞不是某一个孤立的bug,而是执行一类反复发作的老毛病。核心问题出在它处理用户输入的漏洞方式上。Struts2有一套自己的远程表达式语言叫OGNL(Object-Graph Navigation Language),本来是代码用来在框架内部做数据流转的。问题是执行,某些参数在进入OGNL解析器之前没有经过严格过滤,攻击者可以构造恶意表达式,让服务器执行任意代码。
最早的S2-001可以追溯到2007年,影响面相对有限。真正让Struts2“出圈”的是2010年的S2-005和2011年的S2-009。但要说破坏力最大的,还得是2013年的S2-016和2014年的S2-032。S2-016的影响范围覆盖了2.0.0到2.3.15几乎所有版本,国内大量政府网站、银行系统、电商平台都中招了。当时圈子里流传一个段子:你不需要知道目标用什么框架,只要看到URL里带.action后缀,先扔一个S2-016的POC过去试试。
漏洞的利用方式其实不复杂。以S2-016为例,攻击者只需要在URL的action参数后面拼接redirect:或action:前缀,再跟上OGNL表达式,就能触发。比如构造一个redirect:${ #context["xwork.MethodAccessor.denyMethodExecution"]=false},配合后续的Java反射调用,最终执行Runtime.getRuntime().exec()。整个过程不需要认证,不需要任何前置条件,一个GET请求就能拿到服务器权限。
我后来复盘过几个被入侵的案例。有一个电商站点,攻击者通过S2-016上传了一个JSP Webshell,放在/images/目录下,文件名伪装成logo_small.jsp。运维人员排查了三天都没发现,因为那个目录平时根本没人看。另一个案例更离谱,攻击者没有直接拿shell,而是用漏洞读取了数据库配置文件,然后从内网横向移动,最后把整个域控拿下了。Struts2漏洞从来不只是“执行个命令”那么简单,它是进入内网的一块跳板。
Apache官方对这类漏洞的修复态度一直比较被动。S2-016出来之后,他们发了2.3.15.1补丁,但很快就被绕过了。S2-032又补了一次,S2-045、S2-046接着来。每次都是“禁用某些OGNL特性”或者“增加沙箱限制”,但OGNL本身的表达能力太强,沙箱很难做到滴水不漏。这种打补丁的方式本质上是在和攻击者赛跑,而框架的历史包袱又太重,没法推倒重来。
如果你现在还在维护跑着Struts2的老系统,第一件事就是确认版本号。2.5.30之后的版本安全性好了很多,但如果还停留在2.3.x系列,升级到2.5.x可能会遇到兼容性问题——这也是很多团队一直拖着不升的原因。临时缓解措施可以用WAF拦截包含${ 、%{ 、#context等关键字的请求,但这只是权宜之计。真正靠谱的做法是尽快迁移到Spring MVC或Struts2的新版本,别再抱着老版本修修补补了。
说到底,Struts2的RCE漏洞史就是一部“输入信任”的反面教材。框架信任了用户的输入,用户信任了框架的过滤,最后谁都没防住。写代码的人总觉得“这个参数用户改不了”,但攻击者恰恰就盯着你觉得改不了的地方下手。
友情链接