上传绕过_【第9周】上传包可“绕过”Java过滤器的检查?

在分享之前,说明两点。

1.本次分享的内容涉及的知识链相对较长,时间仓促难免存在描述或者理解错误的地方。如有发现,欢迎后台留言。

2.本文写的比较啰嗦,因为想彻底了剖析问题本质。如果想直接了解本次讨论问题的结果,请直接浏览3.2。

 0x01 

背景说明

月初和southwind0师傅做代码审计时,发现了一个比较奇葩的问题。系统设置了全局的XSS过滤器,在其他功能点上生效了,但在一个公告发布功能没有被过滤。southwind0师傅通过对比数据包发现公告发布数据包是上传包(也就是我们常见的上传POST请求)。后来我经过编写测试代码,发现过滤器确实无法过滤上传数据包的参数值。

这让我不禁思考 "上传包可绕过Java过滤器?",如果是真的,那么问题很严重呀,以后过滤器岂不是都可以这样绕过,那这样全局XSS,SQL注入防御过滤器岂不是形同虚设?查了下网上大多数提供XSS过滤器代码基本都存在这个问题,我意识到问题的严重性,打算深入Tomcat和Spring MVC的底层代码一探究竟。

 0x02 

测试代码

由于审计的代码属于敏感信息,我编写了一个和审计场景几乎一样的测试Demo用于本文的研究。测试Demo有get,post和upload页面用于测试Java过滤器对三种类型请求数据包的过滤情况。

f90fed4b684d18b305b78e1b968130b1.png

2.1 后端处理代码

3cc285cac7676301d1b0e8ea929cec22.png

2.2 过滤wrapper代码

7e40234ab3e152b9d6e65ea52926d250.png

2.3 全局过滤器设置

3a023f07ffc2e70060fa430a9f7e1a71.png

想获取完整代码,请到公众号后台回复"上传包绕Java过滤器测试代码"

 0x03 

原理分析

为了方便描述,我这里将请求分文三种,GET型请求,普通POST型请求和上传POST型请求。本文的普通型POST请求指的是处理上传POST型请求,而上传POST型请求就是我们上传包对应的请求。

3.1 Spring MVC如何获取到HTTP请求参数值?

为了更透彻的理解出现该问题的原因,我们需要搞清楚Spring MVC框架是如何获取到前端传来的HTTP请求的参数值。

前端提交的请求会先到达Tomcat服务器,其解析请求参数主要在Request.parseParameters()中进行。

3ed10527202ea75907ca417dbdcf6f6b.png

Tomcat会根据ContentType是否为multipart/form-data判断是否问上传POST型请求,若是则会调用parseParts()来解析,我们继续跟进。由于allowCasualMultipartParsing配置项默认为false,parseParts()直接就返回了,也就是说Tomcat默认不会解析上传POST请求。

ac18f1a01af3a12f733020716f02dec5.png

对针对GET行请求和普通POST,Tomcat会调用parameters.processParameters()方法来解析。我们简单看下它的代码。

7712e853fde5eb5a064b228e5ebb4290.png

至此,Tomcat层面对前端请求解析工作结束。接下来Spring MVC会收到Tomcat传来的HttpServletRequest,此时若请求为上传POST型,Spring MVC会继续调用commons-fileuplad.jar对Tomcat传来的原生Servlet请求类HttpServletRequest的实例进行解析处理。

Spring MVC将原生的HttpServletRequest对象传入CommonsMultipartResolver类的parseRequest()方法进行解析处理。

0981ce723fa50857b7722ff7c6520388.png

CommonsMultipartResolver.parseRequest()方法主要分两步对上传请求进行解析。

  1. 第一步,调用commons-fileupload.jar中的ServletFileUpload类的parseRequest()方法来解析出保存有上传表单各个元素的`FileItem`列表。

  2. 第二步,调用CommonsFileUploadSupport.parseFileItem()方法解析FileItem列表为保存有表单字段名,字段值等信息MultipartParsingResult类型的Map。

下面我们来看下这两步的执行细节。首先第一步最终的处理方法为FileUploadBase.parseRequest()

7d3fc4695394c95280b8f052531c55bc.png

FileUploadBase.parseRequest()解析完会返回一个FileItem实例列表。FileItem就是存储着上传表单的各种元素(字段名,ContentType,是否是简单表单字段,文件名。)本例中我们提交的上传表单的FileItem内容如下:

bca1f40e4352d9266e32664063ad8ce3.png

接着来到第二步,调用CommonsFileUploadSupport.parseFileItem()对commons-fileupload.jar处理的结果---FileItem列表,进行处理。

a5fabdee56fb54f3bb499acdffc81df0.png

最后将上传表单解析的所有元素(multipartFiles,multipartParameters,multipartParameterContentTypes)封装为一个MultipartParsingResult并返回。至此上传POST型请求的解析工作完成。

最后Spring MVC,会使用HandlerMethodInvoker.resolveRequestParam()方法,将解析好的请求参数的值,绑定到不同的对象上,方便Controller层获取。具体我们在下面说。

3.2 上传包无法被过滤的原理

上面我们用较大边幅说明了Spring MVC是如何获取到前端发来的请求的参数值。下面我们就很好理解,问题的所在了。

经过跟踪发现,Spring MVC对各类型请求参数的解析并实现自动绑定,主要在HandlerMethodInvoker.resolveRequestParam()方法。

35ba9ef959c823cd7ac121f1fc30d7de.png

继续跟进到获取参数值的那一步。

d05f838c42f53753e36b982c74f3e7ce.png

通过调式发现,这里如果是GET型和普通POST型请求的话,getRequest()获取到的对象是我们编写的过滤类XssHttpServletRequestWrapper的实例,故调用该对象getParameterValues()来获取值,自然是被过滤了!

若是上传POST行请求的话,getRequest()获取到的是CommonsMultipartResolver类的对象。但实际上调用该对象的getParamterValues()方法,会执行到DefaultMultipartHttpServletRequest类的getParamterValues()类获取值。这是调式发现的,我暂时也没有搞清楚为何,不过不影响我们解决本次研究的问题。

cf2a4d605f1f1e0e520358ddf7f0fc35.png

到这里我们基本明白了,上传包中的参数值没有被过滤,是因为Spring MVC在解析上传包获取其参数值时,没有使用我们编写的过滤类XssHttpServletRequestWrapper中的getParamterValues()方法,而是使用了DefaultMultipartHttpServletRequest类getParamterValuses()。

你可能有疑问,为何SpringMVC获取上传POST请求的参数值时,为啥不调用XssHttpServletRequestWrapper.getParamterValues()来获取呢?

答:因为这样获取不到。

借助以下相关类和接口的继承实现关系图,我们继续看看为何获取不到。

dcdef61f527c32ab1cc8d8e115c3724f.png

结合我们上面对Spring MVC和Tomcat如何解析到请求包的参数值的过程,知道GET型和普通POST型请求包是可以通过HttpServletRequest.getParameterValues()直接获取到对应参数的值,而通过图中可知XssHttpServletRequestWrapper实现了HttpServletRequest,自然也是可以通过XssHttpServletRequestWrapper.getParameterValues()获取到的。

但上传包Tomcat默认没有解析,根据继承关系XssHttpServletRequestWrapper对象中保存的解析结果为Tomcat解析请求的结果,故通过该对象的getParameterValues()方法获取到的参数值为null。也是因此Spring MVC针对Tomcat解析的结果---原生HttpServletRequest,使用common-fileupload.jar来继续解析,得到MultipartHttpServletRequest的实现对象。DefaultMultipartHttpServletRequest类实现了MultipartHttpServletRequest,故通过该类的getParameterValues()方法即可获取到上传POST请求的参数值!

最后特别说明一点,其实上传POST请求数据是流经过过滤器的。没有被过滤,是由于获取参数值的时候,没有调用过滤器Wrapper对象的方法。所以最终我们看到了上传包可以“绕过”过滤器检查的现象。

 0x05 

最后的思考

在文章发布区,评论区,公告区....等功能点上常常需要上传图片或附件,这时表单往往会以上传包的形式提交数据。而这些功能点也是hack们最关注的XSS漏洞测试点,若不注意上传包可"绕过"过滤器的问题,会造成很严重的后果!

我从新翻开了之前审计的项目代码,发现很多Spring MVC项目都是使用过滤器对XSS和SQL注入进行全局防御。而过滤器的代码与本文例子的中过滤器代码相似,很明显都是从网上Copy过来的。这样编写代码是存在问题的,针对这种情况,我们该如何正确防御,我们下周文章详述!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值