JAVA审计-文件上传
前言
本篇记录关于java审计中文件上传部分。
0x01 Commons-FileUpload组件
利用这个组件上传是平时代码上传中遇到最多的。
关于Commons-FileUpload
Commons-FileUpload是Apache的一个组件,依赖于Commons-io,也是目前用的比较广泛的一个文件上传组件之一。
像Spring MVC
、Struts2
、Tomcat
等底层处理文件上传请求都是使用的这个库。
FileUpload上传的基本步骤:
-
创建磁盘工厂:DiskFileItemFactory factory = new DiskFileItemFactory();
-
创建处理工具:ServletFileUpload upload = new ServletFileUpload(factory);
-
设置上传文件大小:upload.setFileSizeMax(3145728);
-
接收全部内容:List items = upload.parseRequest(request);
Commons-FileUpload上传
示例servlet:
@Controller
@WebServlet("/upload")
public class FileUpload extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置文件上传路径
String uploadDir = this.getServletContext().getRealPath("/upload/");
File uploadFile = new File(uploadDir);
//若不存在该路径则创建之
if (!uploadFile.exists()&&!uploadFile.isDirectory()){
uploadFile.mkdir();
}
String message = "";
try {
//创建一个磁盘工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//创建文件上传解析器
ServletFileUpload fileupload = new ServletFileUpload(factory);
//设置上传的文件大小
fileupload.setFileSizeMax(3145728);
//判断是否为multipart/form-data类型,为false则直接跳出该方法
if (!fileupload.isMultipartContent(req)){
return;
}
//使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项
List<FileItem> items = fileupload.parseRequest(req);
for (FileItem item : items) {
//isFormField方法用于判断FileItem类对象封装的数据是否属于一个普通表单字段,还是属于一个文件表单字段,如果是普通表单字段则返回true,否则返回false。
if (item.isFormField()){
String name = item.getFieldName();
//解决普通输入项的数据的中文乱码问题
String value = item.getString("UTF-8");
String value1 = new String(name.getBytes("iso8859-1"),"UTF-8");
System.out.println(name + " : " + value );
System.out.println(name + " : " + value1);
}else {
//获得上传文件名称
String fileName = item.getName();
System.out.println(fileName);
if(fileName==null||fileName.trim().equals("")){
continue;
}
//注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如: c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt
//处理获取到的上传文件的文件名的路径部分,只保留文件名部分
fileName = fileName.substring(fileName.lastIndexOf(File.separator)+1);
//获取item中的上传文件的输入流
InputStream is = item.getInputStream();
//创建一个文件输出流
FileOutputStream fos = new FileOutputStream(uploadDir+File.separator+fileName);
//创建一个缓冲区
byte buffer[] = new byte[1024];
//判断输入流中的数据是否已经读完的标识
int length = 0;
//循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据
while((length = is.read(buffer))>0){
//使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中
fos.write(buffer, 0, length);
}
//关闭输入流
is.close();
//关闭输出流
fos.close();
//删除处理文件上传时生成的临时文件
item.delete();
message = "文件上传成功";
}
}
} catch (FileUploadException e) {
message = "文件上传失败";
e.printStackTrace();
}
}
}
fileupload.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/jdk1_7vulns_war_exploded/upload" enctype="multipart/form-data" method="post">
<p>
用户名: <input name="username" type="text"/>
文件: <input id="file" name="file" type="file"/>
</p>
<input name="submit" type="submit" value="Submit"/>
</form>
</body>
</html>
上面的代码里面对于文件处理只判断了文件是否为空,没有对文件类型进行限制,导致了任意文件上传:
实际中主要是看对后缀的限制,还有一些代码通过new File(item.getContentType()).getName()
得到contentType值判断,也是不严谨的。
0x02 SpringMVC MultipartResolver
使用MultipartResolver
MultipartResolver是专门处理文件上传的一个类
使用之前需要再pom中引入包:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.0.1</version>
</dependency>
MultipartResolver在上下文中没有被装配,需要手动装配MultipartResolver,在springMVC的配置文件dispatcher-Servlet.xml中进行以下的配置:
<!--文件上传配置-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 上传文件大小上限,单位为字节(10485760=10M) -->
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
</bean>
CommonsMultipartFile常用方法:
- String getOriginalFilename():获取上传文件的原名
- InputStream getInputStream():获取文件流
- void transferTo(File dest):将上传文件保存到一个目录文件中
MultipartResolver+IO
示例代码:
@Controller
public class FileUploadController {
//@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
@RequestMapping("/upload")
public String fileUpload(@RequestParam("file") CommonsMultipartFile file , HttpServletRequest request) throws IOException {
//获取文件名 : file.getOriginalFilename();
String uploadFileName = file.getOriginalFilename();
if ("".equals(uploadFileName)){
return "redirect:/index.jsp";
}
System.out.println("上传文件名 : "+uploadFileName);
//上传路径
String path = request.getServletContext().getRealPath("/upload");
//如果路径不存在,创建一个
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
System.out.println("上传文件地址:"+realPath);
InputStream is = file.getInputStream();
OutputStream os = new FileOutputStream(new File(realPath,uploadFileName));
//读取写出
int len=0;
byte[] buffer = new byte[1024];
while ((len=is.read(buffer))!=-1){
os.write(buffer,0,len);
os.flush();
}
os.close();
is.close();
return "redirect:/index.jsp";
}
fileupload2.jsp
<form action="/jdk1_7vulns_war_exploded/upload2" enctype="multipart/form-data" method="post">
<input type="file" name="file"/>
<input type="submit" value="upload">
</form>
@RequestParam("file") CommonsMultipartFile file
直接在前端传入过来的时候就封装成 CommonsMultipartFile 对象处理
CommonsMultipartFile+transferTo
@RequestMapping("/upload2")
public String fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
//上传文件地址
System.out.println("上传文件保存地址:"+realPath);
//通过CommonsMultipartFile的方法直接写文件(注意这个时候)
file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));
return "redirect:/index.jsp";
}
0x03 Servlet Part
servlet3之后,可以不使用commons-fileupload、commons-io这两个jar包来处理文件上传,转而使用request.getParts()
获取上传文件。
此外,servlet3还支持以注解的形式定义一些上传的属性。
上传示例:
String savePath = request.getServletContext().getRealPath("/WEB-INF/uploadFile");
//Servlet3.0将multipart/form-data的POST请求封装成Part,通过Part对上传的文件进行操作。
Part part = request.getPart("file");//通过表单file控件(<input type="file" name="file">)的名字直接获取Part对象
//Servlet3没有提供直接获取文件名的方法,需要从请求头中解析出来
//获取请求头,请求头的格式:form-data; name="file"; filename="snmp4j--api.zip"
String header = part.getHeader("content-disposition");
//获取文件名
String fileName = getFileName(header);
//把文件写到指定路径
part.write(savePath+File.separator+fileName);
总结
对代码中的一些关键函数进行审计:
DiskFileItemFactory
@MultipartConfig
MultipartFile
File
upload
InputStream
OutputStream
write
fileName
filePath
或者有前端代码的话,可以结合multipart/form-data进行定位
参考
https://www.cnblogs.com/CoLo/p/15225367.html
https://www.cnblogs.com/nice0e3/p/13698256.html
https://github.com/proudwind/javasec_study/blob/master/java代码审计-文件操作.md