任意文件上传漏洞 · 攻击Java Web应用

您所在的位置:网站首页 java后端文件上传 任意文件上传漏洞 · 攻击Java Web应用

任意文件上传漏洞 · 攻击Java Web应用

#任意文件上传漏洞 · 攻击Java Web应用| 来源: 网络整理| 查看: 265

任意文件上传漏洞1. Apache commons fileupload文件上传测试2. Servlet 3.0 内置文件上传解析2.1 JSP multipart-config2.2 Servlet @MultipartConfig3. Spring MVC文件上传4. 文件上传 - 编码特性4.1 QP编码4.2 Spring 内置文件名编码特性5. Multipart字段解析问题6. RASP防御恶意文件上传攻击6.1 Apache commons fileupload 防御6.2 javax.servlet.http.Part防御6.3 Spring MVC文件名内置编码支持任意文件上传漏洞

Web应用通常都会包含文件上传功能,用户可以将其本地的文件上传到Web服务器上。如果服务器端没有能够正确的检测用户上传的文件类型是否合法(例如上传了jsp后缀的WebShell)就将文件写入到服务器中就可能会导致服务器被非法入侵。

1. Apache commons fileupload文件上传测试

Apache commons-fileupload是一个非常常用的文件上传解析库,Spring MVC、Struts2、Tomcat等底层处理文件上传请求都是使用的这个库。

示例 - Apache commons-fileupload文件上传:

File upload

用户名: 文件:

示例 - 本地命令执行后门代码:

因为Web应用未检测用户上传的文件合法性导致了任意文件上传漏洞,访问示例中的文件上传地址:http://localhost:8000/modules/servlet/fileupload/file-upload.jsp,并选择一个恶意的jsp后门(示例上传的是一个本地命令执行的后门):

image-20200921003740246

后门成功的写入到了网站目录:

image-20200921003719254

访问命令执行后门测试:http://localhost:8000/uploads/cmd.jsp?cmd=ls,如下图:

image-20200921003841786

2. Servlet 3.0 内置文件上传解析

Servlet3.0 新增了对文件上传请求解析的支持,javax.servlet.http.HttpServletRequest#getParts,使用request.getParts();即可获取文件上传包解析后的结果,从此不再需要使用第三方jar来处理文件上传请求了。

2.1 JSP multipart-config

JSP使用request.getParts();必须配置multipart-config,否则请求时会报错:Unable to process parts as no multi-part configuration has been provided(由于没有提供multi-part配置,无法处理parts)。

在web.xml中添加如下配置:

file-upload-parts.jsp /modules/servlet/fileupload/file-upload-parts.jsp 1000000 1000000 1000000 file-upload-parts.jsp /modules/servlet/fileupload/file-upload-parts.jsp

示例 - file-upload-parts.jsp

File upload

用户名: 文件:

访问示例中的文件上传地址:http://localhost:8000/modules/servlet/fileupload/file-upload-parts.jsp:

image-20201118150151152

文件上传成功:

image-20201118150626809

2.2 Servlet @MultipartConfig

Servlet3.0 需要配置@MultipartConfig注解才能支持multipart解析。

示例 - FileUploadServlet代码:

import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.Collection; @MultipartConfig @WebServlet(urlPatterns = "/FileUploadServlet") public class FileUploadServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { PrintWriter out = resp.getWriter(); out.println("\n" + "\n" + "\n" + " \n" + " File upload\n" + "\n" + "\n" + "\n" + "

\n" + " 用户名: \n" + " 文件: \n" + "

\n" + " \n" + "\n" + "\n" + ""); out.flush(); out.close(); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); String contentType = request.getContentType(); // 检测是否是multipart请求 if (contentType != null && contentType.startsWith("multipart/")) { String dir = request.getSession().getServletContext().getRealPath("/uploads/"); File uploadDir = new File(dir); if (!uploadDir.exists()) { uploadDir.mkdir(); } Collection parts = request.getParts(); for (Part part : parts) { String fileName = part.getSubmittedFileName(); if (fileName != null) { File uploadFile = new File(uploadDir, fileName); out.println(part.getName() + ": " + uploadFile.getAbsolutePath()); FileUtils.write(uploadFile, IOUtils.toString(part.getInputStream(), "UTF-8")); } else { out.println(part.getName() + ": " + IOUtils.toString(part.getInputStream())); } } } out.flush(); out.close(); } }

访问示例中的文件上传地址:http://localhost:8000/FileUploadServlet

image-20201118153002485

文件上传成功:

image-20201118153018149

3. Spring MVC文件上传

Spring MVC会自动解析multipart/form-data请求,将multipart中的对象封装到MultipartRequest对象中,所以在Controller中使用@RequestParam注解就可以映射multipart中的对象了,如:@RequestParam("file") MultipartFile file。

import org.javaweb.utils.FileUtils; import org.javaweb.utils.HttpServletResponseUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; import static org.javaweb.utils.HttpServletRequestUtils.getDocumentRoot; @Controller @RequestMapping("/FileUpload/") public class FileUploadController { @RequestMapping("/upload.php") public void uploadPage(HttpServletResponse response) { HttpServletResponseUtils.responseHTML(response, "\n" + "\n" + "\n" + " \n" + " File upload\n" + "\n" + "\n" + "\n" + "

\n" + " 用户名: \n" + " 文件: \n" + "

\n" + " \n" + "\n" + "\n" + ""); } @ResponseBody @RequestMapping("/upload.do") public Map upload(String username, @RequestParam("file") MultipartFile file, HttpServletRequest request) { // 文件名称 String filePath = "uploads/" + username + "/" + file.getOriginalFilename(); File uploadFile = new File(getDocumentRoot(request), filePath); // 上传目录 File uploadDir = uploadFile.getParentFile(); // 上传文件对象 Map jsonMap = new LinkedHashMap(); if (!uploadDir.exists()) { uploadDir.mkdirs(); } try { FileUtils.copyInputStreamToFile(file.getInputStream(), uploadFile); jsonMap.put("url", filePath); jsonMap.put("msg", "上传成功!"); } catch (IOException e) { jsonMap.put("msg", "上传失败,服务器异常!"); } return jsonMap; } }

访问示例中的文件上传地址:http://localhost:8000/FileUpload/upload.do,如下图:

image-20201116154250929

后门成功的写入到了网站目录:

image-20201116154312441

4. 文件上传 - 编码特性 4.1 QP编码

QP编码( quoted-printable)是邮件协议中的一种内容编码方式,Quoted-printable是使用可打印的ASCII字符(如字母、数字与“=”)表示各种编码格式下的字符,以便能在7-bit数据通路上传输8-bit数据, 或者更一般地说在非8-bit clean媒体上正确处理数据,这被定义为MIME content transfer encoding。

示例 - JavaQP编码代码:

字符串:测试.jsp编码后的结果如下:

image-20201119110638971

QP编码本与文件上传没有什么关系,但是由于在Java中最常用的Apache commons fileupload库从1.3开始支持了RFC 2047 Header值编码,从而支持解析使用QP编码后的文件名。

上传文件的时候选一个文件名经过QP编码后的文件,如:=?UTF-8?Q?=E6=B5=8B=E8=AF=95=2Ejsp?=(测试.jsp)。

示例 - 文件上传测试:

image-20201118171038557

示例 - Payload:

Content-Disposition: form-data; name="file"; filename="=?UTF-8?Q?=E6=B5=8B=E8=AF=95=2Ejsp?="

编码处理类:org.apache.commons.fileupload.util.mime.MimeUtility#decodeText

image-20201116182555363

文件上传成功后文件名被编码成了测试.jsp。

Spring MVC中同样支持QP编码,在Spring中有两种处理Multipart的Resolver: org.springframework.web.multipart.commons.CommonsMultipartResolver和org.springframework.web.multipart.support.StandardServletMultipartResolver。CommonsMultipartResolver使用的是commons fileupload解析的所以支持QP编码。StandardMultipartHttpServletRequest比较特殊,Spring 4没有处理QP编码:

image-20201116190648714

但是在Spring 5修改了实现,如果文件名是=?开始?=结尾的话会调用javax.mail库的MimeDelegate解析QP编码:

image-20201116190416499

javax.mail库不是JDK自带的,必须自行引包,如果不存在该包也将无法解析,SpringBoot + Spring4默认使用的是StandardServletMultipartResolver,但是基于配置的Spring MVC中经常会使用CommonsMultipartResolver,如:

4.2 Spring 内置文件名编码特性

Spring会对文件上传的名称做特殊的处理,org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#parseRequest内置了一种比较特殊的解析文件名的方式,如果传入的multipart请求无法直接使用filename=解析出文件名,Spring还会使用content-disposition解析一次(使用filename*=解析文件名)。

在文件上传时,修改Content-Disposition中的filename=为filename*="UTF-8'1.jpg'1.jsp":

image-20201116202636853

Spring4的org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#parseRequest解析逻辑:

image-20201116200619169

Spring4的org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#extractFilenameWithCharset代码如下:

image-20201116200313346

extractFilenameWithCharset支持对传入的文件名编码,示例中传入的UTF-8'1.jpg'1.jsp会被解析成UTF-8编码,最终的文件名为1.jsp,而1.jpg则会被丢弃。

Spring5的org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#parseRequest除了支持QP编码以外,优化了Spring4的解析文件名的方式:

image-20201116202343036

org.springframework.http.ContentDisposition#parse代码:

image-20201116202037704

文件上传成功:

image-20201116202909113

示例 - Payload:

Content-Disposition: form-data; name="file"; filename*="1.jsp" Content-Disposition: form-data; name="file"; filename*="UTF-8'1.jpg'1.jsp" Content-Disposition: form-data; name="file"; filename*="UTF-8'1.jpg'=?UTF-8?Q?=E6=B5=8B=E8=AF=95=2Ejsp?=" 5. Multipart字段解析问题

在2013年左右,测试过非常多的WAF都不支持Multipart解析,当时经常使用Multipart请求方式来绕过WAF。Multipart所以使用请求与普通的GET/POST参数传输有非常大的区别,因为Multipart请求需要后端Web应用解析该请求包,Web容器也不会解析Multipart请求。WAF可能会解析Multipart但是很多时候可以直接绕过,比如很多WAF无法处理一个数据量较大的Multipart请求或者解析Multipart时不标准导致绕过。

在PHP中默认会解析Multipart请求,也就是说我们除了可以以GET/POST方式传参,还可以使用发送Multipart请求,后端一样可以接受到Multipart中的参数。在Java的MVC框架中Spring MVC、Struts2等实现了和PHP类似的功能,当框架发现请求方式是Multipart时就会主动的解析并将解析结果封装到HttpServletRequest中。

示例 - Spring MVC 注入代码片段:

@ResponseBody @RequestMapping("/getArticleById.php") public SysArticle getArticleByID(String id) { return jdbcTemplate.queryForObject( "select * from sys_article where id = " + id, BeanPropertyRowMapper.newInstance(SysArticle.class) ); }

访问示例程序:http://localhost:8000/getArticleById.php?id=100000:

image-20201118160422872

使用Multipart请求注入数据库信息测试:

image-20201118161532459

6. RASP防御恶意文件上传攻击

RASP不但应该防御Apache commons-fileupload库的文件上传请求,还应当支持Servlet 3.0新增的javax.servlet.http.Part。当检测到请求的文件名称包含了动态脚本文件(如:.jsp/.jspx/.jspf/.jspa/.php/.asp/.aspx等)的 时候需要立即拦截文件上传请求。

6.1 Apache commons fileupload 防御

Apache commons-fileupload底层处理解析Multipart的类是org.apache.commons.fileupload.FileUploadBase.FileItemIteratorImpl.FileItemStreamImpl,如下:

image-20201118163055865

只需Hook FileItemStreamImpl类的构造方法就可以获取到Multipart的字段或者文件名称,RASP只需要检测传入的pName参数值cmd.jsp是否是一个合法的文件名称就可以实现文件上传校验了。

image-20201118163440860

需要注意一点,Tomcat封装了Apache commons fileupload库,并修改了fileupload类的包名,如:org.apache.tomcat.util.http.fileupload.FileUploadBase.FileItemIteratorImpl.FileItemStreamImpl#FileItemStreamImpl,所以应当把这个类也放入检测范围内。

6.2 javax.servlet.http.Part防御

javax.servlet.http.Part是一个接口,不同的容器实现可能都不一样,RASP可以对javax.servlet.http.Part接口的getInputStream方法进行Hook,然后调用getName和getSubmittedFileName就可以获取到字段名称、文件名等信息。

image-20201118165015405

image-20201118165504047

需要特别注意的是Jakarta EE8修改了javax.servlet.http.Part的API包名为:jakarta.servlet.http.Part,为了能够适配高版本的Jakarta API。

6.3 Spring MVC文件名内置编码支持

RASP为了更好的防御文件上传类请求,需要支持RFC 2047的QP编码,还需要支持对Spring MVC内置的文件名编码处理处理。

image-20201118170855641



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3