从代码看任意文件上传漏洞

从代码看任意文件上传漏洞

原创 信安魔方 锐鉴安全 2025-05-04 23:00

声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任。

请大家关注公众号支持,不定期有宠粉福利

Part-01

基础
知识

任意文件上传漏洞属于 Web 应用系统中较为经典、同时也是危害较大的漏洞,在Web1.0 时代,恶意攻击者的主要攻击手法是将可执行脚本(WebShell)上传至目标服务器,以达到控制目标服务器的目的。那时人们的安全意识还不是很强,随着互联网的发展,开发人员的安全意识水平也越来越强,文件上传漏洞得到了明显改善。任意文件上传漏洞的本质是在进行文件上传操作时未对文件类型进行检测或检测功能不规范导致被绕过,从而使攻击者上传的可执行脚本(WebShell)被上传至服务器并成功

解析,如图所示。

针对文件上传,我们在学习渗透测试时已经知道存在各类绕过方法,其中大部分绕过方法都和程序员编写习惯息息相关,本节我们将了解 Java 中各类错误写法引发的文件上传问题。

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

Part-02

文件上传漏洞发现方法

在 Java 开发中,文件上传的方式有多种,我们主要讲解以下三种:通过文件流的方式上传、通过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();Ou
 tputStream 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 "上传失败"; 
}

以上是常见的文件上传方式,类似的文件上传方式很多,这里不一一列举。
文件上传漏洞的本质还是未对文件名做严格校验,常见的主要有如下几种情况:未对文件做任何过滤,仅在前端通过 js 检验,只判断了 Content-Type,后缀过滤不全,读取后缀方式错误等。