multipart/form-data PHP和Java通用的WAF绕过方法
依旧是 multipart/form-data, 去年的时候说道了利用 PHP 的特性去绕过 WAF.轻松绕各种 WAF 的 POST 注入、跨站防御 (比如安全狗)
原文简单的描述了 PHP 在处理 POST 请求的时候会解析 multipart/form-data 的内容。
那么这个 multipart/form-data 到底是个啥呢?
大概长成上面这样 .HTML 代码就更加简单了:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>yzmm - p2j.cn</title> </head> <body> <form action="http://192.168.199.151/index.php" method="POST" enctype="multipart/form-data"> File:<input type="file" name="file" /><br/> ID:<input type="text" name="id" value="select 1 from mysql.user--" style="width:250px;" / ><br/> <input type="submit" value=" 提交" /> </form> </body> </html>
这个特性其实并不只是 PHP 的专利 , 许多其他语言的 MVC 框架为了简化操作也有可能会做类似 PHP FILES 解析。虽说原生的 JSP/Servlet 是不支持解析 multipart 的.但在 Java 语言中当今最火的 SpringMVC、Struts2 都做了一样的事情。你可能经常会看到如下的 Spring MVC 代码:
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; @Controller public class TestController { @RequestMapping("/test1.aspx") public void test1(HttpServletRequest request,HttpServletResponse response){ System.out.println("test1.aspx:"+request.getParameter("username")); } @RequestMapping("/test2.aspx") public void test2(@RequestParam(value = "file", required = false) MultipartFile file,HttpServletRequest request,HttpServletResponse response){ System.out.println("test2.aspx:"+request.getParameter("username")); System.out.println(" 文件名 :"+file.getOriginalFilename()); } }
然后是 HttpClient 客户端测试代码 , 用于发送 HTTP Multipart 测试请求:
import java.io.File; import java.io.IOException; import org.apache.commons.io.IOUtils; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; public class MultipartTest { public static void main(String[] args) { CloseableHttpClient httpClient = HttpClients.createDefault(); HttpPost uploadFile = new HttpPost("http://localhost:8080/test/test1.aspx"); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.addTextBody("username", "admin", ContentType.TEXT_PLAIN); builder.addTextBody("password", "123456", ContentType.TEXT_PLAIN); builder.addBinaryBody("file", new File("/Users/yz/Downloads/bd_logo1_31bdc765.png"), ContentType.APPLICATION_OCTET_STREAM, "pic"); HttpEntity multipart = builder.build(); uploadFile.setEntity(multipart); try { CloseableHttpResponse response = httpClient.execute(uploadFile); HttpEntity responseEntity = response.getEntity(); System.err.println(IOUtils.toString(responseEntity.getContent())); } catch (IOException e) { e.printStackTrace(); } } }
pom 依赖的 jar:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> <version>4.4.4</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.1</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpasyncclient</artifactId> <version>4.1.1</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpmime</artifactId> <version>4.5.1</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency>
test1.do 是一个最普通的 Http 请求 Mapping, 如果当前请求类型是一个 multipart 请求 Spring MVC 会将解析好的 multipart 放到 request 里面 (其实是 Spring MVC 包装了一个 HTTP 请求 , 类名是 :org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest)。于是我们在控制层就拿到了 multipart 里的 username 参数。
毫无疑问 , 使用 MultipartTest 的测试代码去请求 test1.aspx 会输出 multipart 内的 username 的值 :admin.
Struts2 实现方式和 SpringMVC 大同小异,同样的也自动的利用 commons-fileupload 做了 HTTP 解析。
import javax.servlet.http.HttpServletRequest; import org.apache.struts2.ServletActionContext; import com.opensymphony.xwork2.ActionSupport; public class Test extends ActionSupport { private static final long serialVersionUID = 1L; @Override public String execute() throws Exception { HttpServletRequest request = ServletActionContext.getRequest(); System.out.println(request.getParameter("username")); return "input"; } }
那么为什么一个看似很简单的表单数据请求解析的功能会让很多的 WAF 蒙了呢?究其原因主要还是因为 HTTP 请求解析的复杂性和来自客户端的数据不确定性。因为上传一个几十 M 甚至更大的文件需求再平常不过了,如果 WAF 完整的去解析这个 InputStream 会消耗大量的服务器性能有点得不偿失。
另一个原因是由于实现 HTTP 请求的 RFC 的差异性导致次类请求解析得不一致或者解析错误的情况。因为 multipart 解析出问题的还不少。去年 PHP 和 Apache Commons FileUpload 就出过 DOS 漏洞。
Apache Commons FileUpload 和 Apache Tomcat 拒绝服务
Multipart boundary 边界检查问题(分割线长度大于 4091 的)导致拒绝服务(死循环)
PHP 曝 DOS 漏洞可致 CPU 灌满 涉及多个 PHP 版本
那么问题来了 , 各位同学的 SQL 注入和 Struts2 的命令执行漏洞真的修好了吗?看看 loopx9 牛的这个漏洞就知道了 WooYun: 百度某站 st2 命令执行 (独特执行姿势)
赶紧回家修补丁吧。