一、文件上传方式(常见)

通过文件流方式上传;通过ServletFileUpload方式上传;通过MultipartFile方式上传


1、通过文件流方式进行上传

public String fileUpload(@RequestParam("file") CommonsMultipartFile file) throws IOException {
long startTime=System.currentTimeMillis();
System.out.println("fileName:"+file.getOriginalFilename());
try {
OutputStream os=new FileOutputStream("/tmp"+newDate().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";
}


2、通过ServletFileUpload方式上传

String realPath = this.getServletContext().getRealPath("/upload");
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(req)){ return; }
List<FileItem> items =upload.parseRequest(req);
for(FileItem item:items){
if(item.isFormField()){
String filedName = item.getFieldName();
String filedValue = 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();



3、通过MultipartFile方式进行上传

public String handleFileUpload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return "请上传文件";
}
// 获取文件名
String fileName = file.getOriginalFilename();
String suffixName = fileName.substring(fileName.indexOf("."));
String filePath = "/tmp";
File dest = new File(filePath + fileName);
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
try {
file.transferTo(dest);
return "上传成功";
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return "上传失败";
}

在这个代码中,存在一个经典文件上传绕过问题,程序运行时通过如下代码

String suffixName = fileName.substring(fileName.indexOf("."));

获取了上传文件的后缀对其进行判断是否存在于黑名单中。该代码写法看似安全其实很容易犯错误,当文件名为 abc.jpg.jsp时suffixName将等于.jpg.jsp,这明显是不会和黑名单中的后缀相等的,实现了后缀绕过。


二、文件上传代码审计关键词

org.apache.commons.fileupload
java.io.File
MultipartFile
RequestMethod
MultipartHttpServletRequest
CommonsMutipartResolver



三、漏洞审计

通过搜索关键词定位到相应代码段,发现程序使用了MultipartFile方法

JAVA代码审计  任意文件上传篇_上传文件

定位到代码段进行审计,检查是否存在文件上传漏洞

String rootPath = "../webapps/ROOT/upload";
File dir = new File(rootPath + File.separator + "img");
if (!dir.exists())
dir.mkdirs();
String fileName = file.getOriginalFilename();
String contentType = file.getContentType();
String Suffix = fileName.substring(fileName.indexOf("."));
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 type : white_type) {
if (contentType.toLowerCase().equals(type)) {
whiteFlag = true;
break;
}
}
if (!whiteFlag){
return "File type not allowed1";
}
if (BlackFlag){
return "File type not allowed";
}
File serverFile = new File(dir.getAbsolutePath() + File.separator + file.getOriginalFilename());
file.transferTo(serverFile);
return "upload file successfully=" + file.getOriginalFilename();

程序通过如下方法进行对上传文件的判断,通过判断上传文件后缀名是否合法,使用黑白名单的方式进行对照

String Suffix = fileName.substring(fileName.indexOf("."));

该方法看似安全,当文件名为 abc.jpg.jsp时suffixName将等于.jpg.jsp,这明显是不会和黑名单中的后缀相等的,实现了后缀绕过。

JAVA代码审计  任意文件上传篇_后缀_02

在代码中发现程序使用了Contenttype,可以使用BP对其进行抓包并修改Contenttype值,修改filename为1.jpg.jsp进行绕过上传恶意文件到服务器

JAVA代码审计  任意文件上传篇_上传_03

JAVA代码审计  任意文件上传篇_上传_04

四、文件上传漏洞修复

1、设置白名单进行绕过而不是通过黑名单认证的方式

String picwhite[] = {".jpg", ".jpeg", ".png", ".gif", ".bmp"}

2、通过String Suffix = fileName.substring(fileName.lastIndexOf("."))来获取上传文件的真实后缀判断上传文件是否合法而不是通过fileName.substring(fileName.indexOf("."))获取文件后缀来判断上传文件是否合法,避免以黑白名单写法进行绕过。

fileName.substring(fileName.lastIndexOf(".")
fileName.substring(fileName.indexOf("."))

3、通过String whitetype[]={"image/gif","image/jpeg","image/jpg","image/png"} 对文件类型进行判断,虽然可以通过BP抓包修改MIME类型但是有一定的限制,通过验证后在文件存储时又对文件进行随机重命名。

4、全局过滤关键字