长亭百川云 - 文章详情

Apache Struts2 文件上传漏洞分析(CVE-2023-50164)

中孚安全技术研究

72

2024-07-13

1. 前言

官方公告**:**
https://cwiki.apache.org/confluence/display/WW/S2-066

漏洞描述:
攻击者可以操纵文件上传参数以启用路径遍历,在某些情况下,这可能导致上传可用于执行远程代码执行的恶意文件。

影响版本:
Struts 2.0.0 - Struts 2.3.37 (EOL)
Struts 2.5.0 - Struts 2.5.32
Struts 6.0.0 - Struts 6.3.0

2. 环境搭建

新建一个项目,Archetype 选择org.apache.maven.archetypes:maven-archetype-webapp;

pom.xml 中添加 Struts2 依赖,以 6.3.0 版本为例,tomcat 也添加上,以便获取当前路径;

`<dependency>`  `<groupId>org.apache.struts</groupId>`  `<artifactId>struts2-core</artifactId>`  `<version>6.3.0</version>``</dependency>``<dependency>`  `<groupId>org.apache.tomcat</groupId>`  `<artifactId>tomcat-catalina</artifactId>`  `<version>8.5.81</version>``</dependency>`

web.xml 中配置过滤器:

`<!DOCTYPE web-app PUBLIC` `"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"` `"http://java.sun.com/dtd/web-app_2_3.dtd" >``<web-app>`  `<display-name>Archetype Created Web Application</display-name>`  `<filter>`    `<filter-name>struts2</filter-name>`    `<filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>`  `</filter>`  `<filter-mapping>`    `<filter-name>struts2</filter-name>`    `<url-pattern>*.action</url-pattern>`  `</filter-mapping>``</web-app>`

文件上传的 action:

`package blckder02.struts2.action;``import com.opensymphony.xwork2.ActionSupport;``import org.apache.commons.io.FileUtils;``import java.io.File;``import java.io.IOException;``publicclass UploadAction extends ActionSupport {`    `private File myfile;`    `private String myfileContentType;`    `private String myfileFileName;`    `private String destpath;`    `public String execute() {`        `destpath = ServletActionContext.getServletContext().getRealPath("/")+"uploads\\upload\\";`        `try{`            `System.out.println("Src File name: " + myfile);`            `System.out.println("Dst File name: " + myfileFileName);`            `File destFile = new File(destpath, myfileFileName);`            `FileUtils.copyFile(myfile, destFile);`            `return SUCCESS;`        `} catch (IOException | NullPointerException e) {`            `e.printStackTrace();`            `return ERROR;`        `}`    `}`    `public File getMyfile() {`        `return myfile;`    `}`    `public void setMyfile(File myfile) {`        `this.myfile = myfile;`    `}`    `public String getMyfileContentType() {`        `return myfileContentType;`    `}`    `public void setMyfileContentType(String myfileContentType) {`        `this.myfileContentType = myfileContentType;`    `}`    `public String getMyfileFileName() {`        `return myfileFileName;`    `}`    `public void setMyfileFileName(String myfileFileName) {`        `this.myfileFileName = myfileFileName;`    `}``}`

uopload.jsp:

`<%@ page contentType="text/html;charset=UTF-8" language="java" %>``<%@ taglib prefix="s" uri="/struts-tags"%>``<html>``<head>`    `<title>Upload</title>``</head>``<body>``<h2>Upload</h2><br/>``<form action="upload.action" method="post" enctype="multipart/form-data">`    `<s:label for="myfile">Please upload your file</s:label><br/>`    `<input type="file" name="myfile"/>`    `<input type="submit" value="Upload"/>``</form>``</body>``</html>`

success.jsp:

`<%@ page contentType="text/html;charset=UTF-8" language="java" %>``<%@ taglib prefix="s" uri="/struts-tags"%>``<html>``<head>`    `<title>Success</title>``</head>``<body>``You have successfully uploaded <s:property value="myfileFileName"/>``</body>``</html>`

struts.xml 中配置 action 的解析,继承了 struts-default 包;

`<?xml version="1.0" encoding="UTF-8"?>``<!DOCTYPE struts PUBLIC`        `"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"`        `"http://struts.apache.org/dtds/struts-2.0.dtd">``<struts>`    `<package name="default" namespace="/" extends="struts-default">`        `<action name="upload" class="blckder02.struts2.action.UploadAction" method="execute">`            `<result name="success">/success.jsp</result>`            `<result name="error">/upload.jsp</result>`        `</action>`    `</package>``</struts>`

struts-default 包中使用了FileUploadInterceptor和ParametersInterceptor,在这里也是继承使用的,会对上传的文件以及参数进行拦截校验;

最后配置上 Tomcat 就可以运行了。

3. 漏洞复现

准备一个含有简单 jsp 木马的文件;

选择文件,上传抓包;

发送到 Repeater,修改请求包,将myfile参数名的第一个字母改为大写,再构造一个myfileFileName参数,参数值为想要文件保存后所在的路径及文件名。构造的参数名必须是第一个参数名+"FileName",不能随便构造。
上传成功后可以看到文件名为自定义的参数值;

访问 jsp,能成功执行命令,文件保存到了 /uploads 目录下,且文件名保存为构造传入的exec.jsp。

4. 漏洞分析

断点跟踪一下文件上传的流程,可以从org.apache.struts2.dispatcher.Dispatcher#serviceAction()断点,开始处理文件上传的 action;
可以看到 request 中含有一个文件类型参数files和一个字符串类型的参数params;

跟进,将myfileFileName参数封装成了 HttpParameters 对象,添加到了context中;

回到serviceAction()中,extraContext是前面createContextMap()创建的 context 对象,这里生成了一个 action 的代理对象,开始执行 action 流程。

接着进入 FileUploadInterceptor,从上传的请求中提取了文件、Content-Type 和文件名;可以看到保存文件名的键名是inputName + "FileName",所以请求包中构造的参数名也得是这种形式,才能完成覆盖。

在调用multiWrapper.getFileNames()的时候,对文件名中/、\符号前的字符串进行了过滤,所以直接在文件名中进行路径穿越是不行的;

把这三个值添加到 HttpParameters 对象中后,actioncontext 里的参数就有四个了。

进入 ParametersInterceptor,在setParameters()中,将 HttpParameters 对象中的参数依次放入 TreeMap 对象中;

可以看到 TreeMap 对象中保存的参数顺序变了,是因为 TreeMap 对象会按参数名的大小顺序从小到大保存值;
在TreeMap.put()方法中,会将新添入的参数名与已保存的参数名做比较;

比较逻辑就是逐字符比较,遇到不相同的字符就返回新增参数名与已保存参数名 ASCII 的差值;

差值为负,则说明新增参数名比已保存参数名的 ASCII 值小,将新增参数插入到已保存参数的前面,所以要保证构造的文件名比inputName + "FileName"生成的文件名的 ASCII 值大。

newStack.setParameter()进行参数绑定,遍历后myfileFileName的值已经被赋为S2-66.txt;

跟进,调用task.execute()执行表达式;

一直跟进,会再次调用 setter 方法为myfileFileName赋值,所以第一次赋值的S2-66.txt就被覆盖为../exec.jsp了。

最后在 UploadAction 中保存文件,路径中拼接的文件名包含路径穿越符,便会解析保存到上一目录。

5. 补丁分析

在 6.3.0.2版本中,HttpParameters 类的appendAll()中添加调用了remove()方法;遍历检查参数是否需要删除,remove()方法中添加使用了equalsIgnoreCase()方法来忽略大小写进行比较。

断点进入,将MyfileFileName与myfileFileName带入校验;

跟进,在StringLatin1.regionMatchesCI()中,将两个参数名进行了统一的大小写转换再进行比较,这里的比较结果相同,返回 true;

于是将已经存在的同名参数(忽略大小写)删除了,后面就不存在二次调用 setter 方法来覆盖了。

参考链接:
https://y4tacker.github.io/2023/12/09/year/2023/12/Apache-Struts2-文件上传分析-S2-066/
https://xz.aliyun.com/t/13172

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2