HacKerQWQ的博客空间

javaweb代码审计学习(文件上传漏洞)

Word count: 1.4kReading time: 7 min
2021/11/29 Share

环境配置

在IDEA中创建Spring项目,在包下创建如下代码

image-20211129160349817

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
package com.example.demo;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;

import java.io.*;

@RestController
public class Streamupload {
@RequestMapping("/streamupload")
public String fileUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request)throws IOException{
// long startTime = System.currentTimeMillis();
System.out.println(request.getServletContext().getRealPath(File.separator));
System.out.println("fileName: "+file.getOriginalFilename());
try{
OutputStream os = new FileOutputStream("./upload/tmp"+new Date().getTime()+file.getOriginalFilename());
InputStream is = file.getInputStream();
int temp;
while((temp=is.read())!=-1){
os.write(temp);
}
os.flush();
os.close();
is.close();
}catch (FileNotFoundException e){
e.printStackTrace();
}
return "success!";
}
}

DemoApplication中点击开始即可运行Spring程序,接着创建upload.html上传文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form method="POST" enctype="multipart/form-data" action="http://192.168.43.238:7070/streamupload">
<input type="file" name="file">
<input type="submit" name="submit">
</form>
</body>
</html>

上传成功返回success

image-20211129160551505

java代码中常见的文件上传方式

文件流上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestController
public class Streamupload {
@RequestMapping("/streamupload")
public String fileUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request)throws IOException{
long startTime = System.currentTimeMillis();
System.out.println(request.getServletContext().getRealPath(File.separator));
System.out.println("fileName: "+file.getOriginalFilename());
try{
OutputStream os = new FileOutputStream("./upload/tmp"+new Date().getTime()+file.getOriginalFilename());
InputStream is = file.getInputStream();
int temp;
while((temp=is.read())!=-1){
os.write(temp);
}
os.flush();
os.close();
is.close();
}catch (FileNotFoundException e){
e.printStackTrace();
}
return "success!";
}
}

ServletFileUpload上传

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
@RestController
public class ServletUpload {
@RequestMapping("/supload")
public String fileupload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws FileUploadException, IOException {
String realPath = "./upload";
// return realPath;
String tempPath = "./tmp";
File f = new File(realPath);
if (!f.exists()&&!f.isDirectory()){
f.mkdir();
}
File f1 = new File(tempPath);
if (!f1.isDirectory()){
f1.mkdir();
}
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(f1);
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
if (!ServletFileUpload.isMultipartContent(request)){return "error";}
List<FileItem> items = upload.parseRequest(request);
for (FileItem item:items){
if (item.isFormField()){
String fieldName = item.getFieldName();
String fieldValue = item.getString("UTF-8");
}else{
String fileName = item.getName();
if (fileName==null||"".equals(fileName.trim())){continue ;}
fileName = fileName.substring(fileName.lastIndexOf("/")+1);
String filePath = realPath+"/"+fileName;
InputStream in = item.getInputStream();
OutputStream out = new FileOutputStream(filePath);

byte b[]=new byte[1024];
int len = -1;
while((len=in.read(b))!=-1){
out.write(b,0,len);
}
out.close();
in.close();
try{
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
item.delete();
}
}
return "success";
}
}

MultipartFile方式上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestController
public class MultiUpload {
@RequestMapping("/mupload")
public String handleFileUpload(@RequestParam("file") MultipartFile file){
if (file.isEmpty()){
return "请上传文件";
}
// 获取文件名
String fileName = file.getOriginalFilename();
String suffix = fileName.substring(fileName.indexOf("."));
String filePath = System.getProperty("user.dir")+"/tmp";
File dest = new File(filePath+File.separator+fileName);
if (!dest.getParentFile().exists()){
dest.getParentFile().mkdirs();
}
try{
file.transferTo(dest);
return "上传成功";
} catch (IOException e) {
e.printStackTrace();
return "上传失败";
}
}
}

文件上传漏洞简介

任意文件上传漏洞的本质是在进行文件上传操作时未对文件类型进行检测或检测功能不规范导致被绕过,从而使攻击者上传的可执行脚本(WebShell)被上传至服务器并成功解析。

漏洞危害:获取WebShell,攻击内网,破坏服务器数据等。

一个简单的存在文件上传漏洞的例子:

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
@RestController
public class MultiUpload {
@RequestMapping("/mupload")
public String handleFileUpload(@RequestParam("file") MultipartFile file){
if (file.isEmpty()){
return "请上传文件";
}
// 获取文件名
String fileName = file.getOriginalFilename();
String suffix = fileName.substring(fileName.indexOf("."));//获取第一个.后面的字符串
String contentType = file.getContentType();

//过滤
String[] picBlack = {".jsp",".jspx",".bat",".exe",".vbs"};
String[] white_type = {"image/gif","image/jpeg","image/jpg","image/png"};
Boolean BlackFlag = false;
Boolean WhiteFlag = false;
for (String black_suffix:picBlack){
if (suffix.toLowerCase().equals(black_suffix)){
BlackFlag = true;
break;
}
}
for (String white_suffix:white_type){
if (contentType.toLowerCase().equals(white_suffix)){
WhiteFlag = true;
break;
}
}
if (BlackFlag||!WhiteFlag){
return "File Type not allow";
}

String filePath = System.getProperty("user.dir")+"/tmp";
File dest = new File(filePath+File.separator+fileName);
if (!dest.getParentFile().exists()){
dest.getParentFile().mkdirs();
}
try{
file.transferTo(dest);
return "上传成功";
} catch (IOException e) {
e.printStackTrace();
return "上传失败";
}
}
}

上面的代码就是很典型的文件上传漏洞,只对第一个.后面的字符串进行黑名单拦截,及其容易被绕过,如.txt.jsp

文件上传漏洞修复

  1. 对上传文件进行随机字符串重命名
  2. 用白名单限制文件后缀
  3. 去除文件名中的特殊字符
  4. 如果是上传图片,使用图片库对文件内容进行检测

示例代码:

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
@RestController
public class securityUpload {
@RequestMapping("/securityUpload")
public String handleFileUpload(@RequestParam("file") MultipartFile file){
if (file.isEmpty()){
return "请上传文件";
}
// 获取文件名
String fileName = file.getOriginalFilename();
String suffix = fileName.substring(fileName.lastIndexOf("."));
String contentType = file.getContentType();

//过滤
String[] picWhite = {".png",".jpg",".gif",".webp",".bmp"};
String[] white_type = {"image/gif","image/jpeg","image/jpg","image/png"};
Boolean SuffixFlag = false;
Boolean TypeFlag = false;
for (String pic_suffix:picWhite){
if (suffix.toLowerCase().equals(pic_suffix)){
SuffixFlag = true;
break;
}
}
for (String white_suffix:white_type){
if (contentType.toLowerCase().equals(white_suffix)){
TypeFlag = true;
break;
}
}
if (!SuffixFlag||!TypeFlag){
return "File Type not allow";
}

String filePath = System.getProperty("user.dir")+"/tmp";

Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddhhmmss");
String newfileName = dateFormat.format(date)+Integer.toHexString((int)new Date().getTime())+suffix;

File dest = new File(filePath+File.separator+newfileName);
if (!dest.getParentFile().exists()){
dest.getParentFile().mkdirs();
}
try{
file.transferTo(dest);
return "上传成功";
} catch (IOException e) {
e.printStackTrace();
return "上传失败";
}
}
}

image-20211129201548516

代码审计中文件上传关键字

1
2
3
4
5
6
7
org.apache.commons.fileupload
java.io.File
MultipartFile
RequestMethod
MultipartHttpServletRequest
CommonsMultipartResolver
...
CATALOG
  1. 1. 环境配置
  2. 2. java代码中常见的文件上传方式
    1. 2.1. 文件流上传
    2. 2.2. ServletFileUpload上传
    3. 2.3. MultipartFile方式上传
  3. 3. 文件上传漏洞简介
  4. 4. 文件上传漏洞修复
  5. 5. 代码审计中文件上传关键字