赞
踩
Springboot:2.7.5
依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <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> </dependencies>
application.yml
spring:
mvc:
view:
prefix: /WEB-INF/jsp/
suffix: .jsp
web:
resources:
static-locations: classpath:/templates/
server:
port: 8081
multipart/form-data这种编码方式的表单会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数里。通常会见到配合method=post去搭配使用,而后端采取inputstream等方式读取客户端传入的二进制流来处理文件。
PHP中:PHP<5.3.29,且GPC关闭
Java中:
同时考虑到00截断绕过的问题,在JDK1.7.0_40(7u40)开始对\00进行了检查:
final boolean isInvalid(){
if(status == null){
status=(this.path.indexOf('\u0000')<0)?PathStatus.CHECKED:PathStatus.INVALID;
}
return status == PathStatus.INVALID;
}
在7u40后这个问题也就修复了
主要用来做分隔符,防止因为跨平台时各个操作系统之间分隔符不一样出现问题
public static final char separatorChar
与系统有关的默认名称分隔符。此字段被初始化为包含系统属性 file.separator 值的第一个字符。在 UNIX 系统上,此字段的值为 ‘/’;在 Microsoft Windows 系统上,它为 ‘\’
主要用来做分隔符,防止因为跨平台时各个操作系统之间分隔符不一样出现问题
public static final String separator = "" + separatorChar;
其实separator是由separatorChar转换成的,所以只是类型不同
将字符串与指定的对象比较,不考虑大小写。文件上传中主要用于判断文件文件后缀名
可以与equlas对比来看,s1和s2只有大小写不同,如果用equals则返回false,equalsIgnoreCase返回true
String s1 = "SENTIMENT";
String s2 = "sentiment";
System.out.println(s1.equals(s2)); //false
System.out.println(s1.equalsIgnoreCase(s2)); //true
@RequestMapping("/upload1") public String fileUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws IOException { String path = request.getServletContext().getRealPath("upload"); String filename = file.getOriginalFilename(); if (file.isEmpty()) { return "请上传文件"; } try { OutputStream fos = new FileOutputStream(path + "/" + filename); InputStream fis = file.getInputStream(); int len; while ((len = fis.read()) != -1) { fos.write(len); } fos.flush(); fos.close(); fis.close(); return "Success!"; } catch (FileNotFoundException e) { e.printStackTrace(); } return ""; }
<h1>文件流上传</h1>
<form method="POST" enctype="multipart/form-data" action="http://127.0.0.1:8081/upload1">
<input type="file" name="file">
<input type="submit" name="submit">
</form>
@RequestMapping("/file2") public String MultiFileUpload(@RequestParam("file") MultipartFile file ,HttpServletRequest request) { if (file.isEmpty()) { return "请上传文件"; } String filePath = request.getServletContext().getRealPath("upload"); String fileName = file.getOriginalFilename(); File dest = new File(filePath + File.separator + fileName); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } try { file.transferTo(dest); return "Success!"; } catch (IOException e) { e.printStackTrace(); } return ""; }
若要对上传内容进行限制则可设置:
springboot
spring:
servlet:
multipart:
enabled: true
# 单文件大小
max-file-size: 100MB
# 文件达到多少磁盘写入
file-size-threshold: 4MB
springmvc
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 需要与jsp中的pageEncoding配置一致,默认为iso-8859-1-->
<property name="defaultEncoding" value="utf-8"/>
<!-- 单文件大小,单位为字节10485700=100M-->
<property name="maxUploadSize" value="10485700"/>
<!-- 文件达到多少磁盘写入-->
<property name="maxInMemorySize" value="409600"/>
</bean>
<h1>MultipartFile上传</h1>
<form method="POST" enctype="multipart/form-data" action="http://127.0.0.1:8081/upload2">
<input type="file" name="file">
<input type="submit" name="submit">
</form>
依赖
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.2</version>
</dependency>
Springboot环境需关闭multipart
spring:
servlet:
multipart:
enabled: false
@RequestMapping("/upload3") protected void ServletFileUpload(HttpServletRequest request, HttpServletResponse response) throws IOException { { //设置文件上传路径 String filePath = request.getServletContext().getRealPath("upload"); File uploadFile = new File(filePath); //若不存在该路径则创建之 if (!uploadFile.exists() && !uploadFile.isDirectory()) { uploadFile.mkdir(); } try { //创建一个磁盘工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); //创建文件上传解析器 ServletFileUpload fileupload = new ServletFileUpload(factory); //三个照顾要上传的文件大小 fileupload.setFileSizeMax(3145728); //判断是否为multipart/form-data类型,为false则直接跳出该方法 if (!fileupload.isMultipartContent(request)) { return; } //使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项 List<FileItem> items = fileupload.parseRequest(request); 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(filePath + File.separator + fileName); byte buffer[] = new byte[1024]; int length = 0; while ((length = is.read(buffer)) > 0) { fos.write(buffer, 0, length); } is.close(); fos.close(); item.delete(); } } response.getWriter().write("Success!"); } catch (FileUploadException e) { e.printStackTrace(); } } }
<h1>ServletFileUpload上传</h1>
<form method="POST" enctype="multipart/form-data" action="http://127.0.0.1:8081/upload3">
<input type="file" name="file">
<input type="submit" name="submit">
</form>
Servlet3之后,有提出了request.getParts()
获取上传文件的方式。
除此外若加上注解@MultipartConfig,则可定义一些上传属性
方法 | 类型 | 是否可选 | 作用 |
---|---|---|---|
fileSizeThershold | int | 是 | 当前数据量大于该值时,内容将被写入文件 |
location | String | 是 | 存放文件的路径 |
maxFileSize | long | 是 | 允许上传的文件最大值,默认为-1,表示没有限制 |
maxRequestSize | long | 是 | 针对multipart/form-data 请求的最大数量,默认为-1,表示没有限制 |
@RequestMapping("/upload4") public void ServletPartUpload(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String filePath = request.getServletContext().getRealPath("upload"); File uploadFile = new File(filePath); //若不存在该路径则创建之 if (!uploadFile.exists() && !uploadFile.isDirectory()) { uploadFile.mkdir(); } //通过表单中name属性值,获取filename Part part = request.getPart("file"); if(part == null) { return ; } String filename = filePath + File.separator + part.getSubmittedFileName(); part.write(filename); part.delete(); }
<h1>ServletPart上传</h1>
<form method="POST" enctype="multipart/form-data" action="http://127.0.0.1:8081/upload4">
<input type="file" name="file">
<input type="submit" name="submit">
</form>
上述都是no waf的文件上传方式,若不做任何防御的情况下,可以实现任意文件上传,造成文件上传漏洞
通过上述任意方法,上传jsp马
<%
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int a;
byte[] b = new byte[1024];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.println(new String(b,0,a));
}
%>
执行成功
//1、MIME检测
String contentType = file.getContentType();
String[] white_type = {"image/gif","image/jpeg","image/jpg","image/png"};
Boolean ctFlag = false;
for (String suffix:white_type){
if (contentType.equalsIgnoreCase(suffix)){
ctFlag = true;
break;
}
}
if (!ctFlag){
return "content-type not allow";
}
如果单设置这一个的话其实很好绕过
可以用uuid、md5、时间戳等方式
//2、重命名文件
String uuid = UUID.randomUUID().toString();
fileName = uuid+fileName.substring(fileName.lastIndexOf("."));;
//3、后缀白名单
String fileSuffix = fileName.substring(fileName.lastIndexOf("."));
String[] white_suffix = {"gif","jpg","jpeg","png"};
Boolean fsFlag = false;
for (String suffix:white_suffix){
if (contentType.equalsIgnoreCase(fileSuffix)){
fsFlag = true;
break;
}
}
if (!fsFlag){
return "suffix not allow";
}
绕过MIME检测后,可以通过白名单进行进一步的防御
可以将图片存放到不可访问的路径,例如:Servlet的WEB-INF下,默认情况是访问不到的
//4、修改存储位置
String filePath = request.getServletContext().getRealPath("/WEB-INF/upload");
最终代码
public String MultiFileUpload(@RequestParam("file") MultipartFile file ,HttpServletRequest request) { if (file.isEmpty()) { return "请上传文件"; } // String filePath = request.getServletContext().getRealPath("upload"); String fileName = file.getOriginalFilename(); //1、MIME检测 String contentType = file.getContentType(); String[] white_type = {"image/gif","image/jpeg","image/jpg","image/png"}; Boolean ctFlag = false; for (String suffix:white_type){ if (contentType.equalsIgnoreCase(suffix)){ ctFlag = true; break; } } if (!ctFlag){ return "content-type not allow"; } //2、重命名文件 String uuid = UUID.randomUUID().toString(); fileName = uuid+fileName.substring(fileName.lastIndexOf("."));; //3、后缀白名单 String fileSuffix = fileName.substring(fileName.lastIndexOf(".")); String[] white_suffix = {"gif","jpg","jpeg","png"}; Boolean fsFlag = false; for (String suffix:white_suffix){ if (contentType.equalsIgnoreCase(fileSuffix)){ fsFlag = true; break; } } if (!fsFlag){ return "suffix not allow"; } //4、修改存储位置 String filePath = request.getServletContext().getRealPath("/WEB-INF/upload/"); File dest = new File(filePath + File.separator + fileName); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } try { file.transferTo(dest); return "Success!"; } catch (IOException e) { e.printStackTrace(); } return ""; }
DiskFileItemFactory
@MultipartConfig
MultipartFile
File
upload
InputStream
OutputStream
write
fileName
filePath
Java审计之文件上传 - Zh1z3ven - 博客园 (cnblogs.com)
javasec_study/java代码审计-文件操作.md at master · proudwind/javasec_study (github.com)
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。