奇怪的XSS过滤器
  一般在实际Web开发中,我们常常需要过滤/编码页面传递给后台的特殊字符,防止xss跨站脚本攻击。使用过滤器Filter可以很好的完成这个功能。前段时间某项目的研发使用过滤器进行XSS过滤后,进行复测,发现一个奇怪的现象,原本网站全局的xss,变成了只有部分接口存在xss。
一开始以为过滤器的匹配接口路径存在问题,查看代码发现开发是通过写HttpServletRequestWrapper的Method对所有的request进行处理的:
相关代码如下,系统使用SSM开发:
首先定义一个filter,通过重写HttpServletRequestWrapper的方法修改request参数:
java
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
SecHttpRequestWrapper reqwrapper = new SecHttpRequestWrapper((HttpServletRequest) request);
System.out.println("filter in");
chain.doFilter(reqwrapper, response);
return;
}
继承HttpServletRequestWrapper,重写getParameter方法,将参数名和参数值都做xss过滤:
```java
public class SecHttpRequestWrapper extends HttpServletRequestWrapper {
private HttpServletRequest request;
public SecHttpRequestWrapper(HttpServletRequest request){
super(request);
this.request = request;
}
/**
* 覆盖getParameter方法,将参数名和参数值都做xss过滤。<br/>
*/
@Override
public String getParameter(String name) {
if (value != null) {
value = xssEncode(value);
}
return value;
}
public static String xssEncode(String value) {
//xss处理
}
}
  通过上面的操作,前端传递过来的内容就可以在Controller中使用request.getParameter("xxx")的形式获取到之后,然后进行xss编码处理,从而在进行view层展示或者写入数据库前保证系统免受xss跨站脚本攻击的困扰了。
java
  例如如下测试接口:
@RequestMapping(value = "/servlet_req_param",method = { RequestMethod.POST, RequestMethod.GET })
public String req_param(HttpServletRequest request){
return request.getParameter("content");
}
```
将content参数的值设置为alert(1),访问测试接口debug查看在经过过滤器处理后的值:
可以看到输入的恶意xss payload的确是经过相关的编码操作的。
这么梳理下来好像的却是这么回事,但是结合测试,的确是过滤器失效了部分接口仍可进行xss利用。看一下仍可进行xss利用的接口,发现一个问题就是都使用了@RequestParam注解和POJO对象绑定的方式进行传参。
复盘
刚刚提到,问题接口都使用了@RequestParam注解和POJO对象绑定的方式进行传参。
在实际开发中,软件框架可以自动将HTTP请求参数绑定到程序代码变量或者对象中,从而使得更易开发。在 SpringMVC 中,提交请求的数据是通过方法形参来接收的。从客户端请求的 key/value 数据,经过参数绑定,将 key/value 数据绑定到 Controller 的形参上,然后在 Controller 就可以直接使用该形参。
那么就来看看@RequestParam注解和POJO对象绑定的方式跟过滤器之间有什么联系好了。
使用@RequestParam注解进行参数绑定,测试接口如下:
java
@RequestMapping(value = "/req_param2",method = { RequestMethod.POST, RequestMethod.GET })
public String req_param(ModelMap modelmap, @RequestParam("content") String content) {
return content;
}
将content参数的值设置为alert(1),访问测试接口debug查看在经过过滤器处理后的值:
这里输入的content经过filter后原样输出了,并没有经过xssEncode()方法进行过滤/编码操作。
再试试POJO对象绑定的方式,测试接口如下:
java
@RequestMapping(value = "/user_model_req",method = { RequestMethod.POST, RequestMethod.GET })
public User user_model_req(ModelMap modelmap, User user) {
if (user == null) {
return null;
}
return user;
}
User类:
```java
public class User {
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
private String username;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
private String password;
}
  跟前面的一样,将User属性的值设置为<script>alert(1)</script>,访问测试接口debug查看在经过过滤器处理后的值:
java

  这里输入的User属性经过filter后同样也原样输出了,并没有经过xssEncode()方法进行过滤/编码操作。
  再试试基本String类型的参数绑定,测试接口如下:
@RequestMapping(value = "/req_param1",method = { RequestMethod.POST, RequestMethod.GET })
public String req_var(ModelMap modelmap, String content) {
return content;
}
```
同样的,将content参数的值设置为alert(1),访问测试接口debug查看在经过过滤器处理后的值:
以上的结果都跟我们的预期相悖,出现了Filter无法覆盖的场景。
再回头看一下我们的filter,我们是通过重写HttpServletRequestWrapper的getParameter()方法进行参数编码/过滤的,也就是说:
springmvc直接绑定pojo或者@RequestParam(基本数据类型绑定)没法在HttpServletRequestWrapper的getparameter方法进行过滤操作。
到底漏了什么
猜测很大可能是跟HttpServletRequestWrapper有关系。查看相关的doc.https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequestWrapper.html,可以看到,除了getparameter方法以外,HttpServletRequestWrapper因为继承自javax.servlet.http.HttpServletRequest,还存在多种方法:
* getParameterValues
* getParameterNames
* getParameterMap
* ......
那么上面无法覆盖的问题很可能是直接绑定pojo或者@RequestParam(基本数据类型绑定)获取参数值的方法不一致,从而导致再重写HttpServletRequestWrapper的Method后无法满足我们的安全需要。
查看注解@RequestParam的具体实现方式,在org.springframework.web.bind.annotation.support.HandlerMethodInvoker 的 resolveRequestParam中看到如下代码:
java
if (paramValue == null) {
String[] paramValues = webRequest.getParameterValues(paramName);
if (paramValues != null) {
paramValue = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
if (paramValue == null) {
if (defaultValue != null) {
paramValue = resolveDefaultValue(defaultValue);
}
else if (required) {
raiseMissingParameterException(paramName, paramType);
}
paramValue = checkValue(paramName, paramValue, paramType);
}
那么也就是说注解@RequestParam是使用 getParameterValues 方法来获取参数值的,而不是 getParameter方法。所以只需要重写HttpServletRequestWrapper的getParameterValues 方法应该就可以解决上述无法覆盖的问题了。
下面实验看一看,重写getParameterValues 方法后打印下log:
java
public String[] getParameterValues(String parameter) {
System.out.println("filter in getParameterValues Method");
String[] values = super.getParameterValues(parameter);
if (values==null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = xssEncode(values[i]);
}
return encodedValues;
}
跟前面的一样,将content参数的值设置为alert(1),访问测试接口debug查看在经过过滤器处理后的值:
可以看到成功在getParameterValues拦截了我们的输入并且在xssEncode()方法进行了过滤/编码操作。印证了前面的想法。
同理,POJO绑定猜测也是通过getParameterValues进行参数值的获取,这里将User属性的值设置为alert(1),访问测试接口debug查看在经过过滤器处理后的值:
可以看到成功在getParameterValues拦截了我们的输入并且在xssEncode()方法进行了过滤/编码操作,也就是说POJO绑定猜测也是通过getParameterValues进行参数值的获取的。
也就是说,springmvc直接绑定pojo或者@RequestParam(基本数据类型绑定)可以在HttpServletRequestWrapper的getparameterValues方法进行过滤操作。另外搭建了一下环境测试了一下Springboot,也是同样的效果。
总结与延伸
filter的实际开发设计时需要关注的点很多,一不注意很可能就会导致我们的安全措施被绕过。在通过重写HttpServletRequestWrapper的Method进行filter设计的时候,要额外关注上述的细节。根据上述场景,进一步引申,可能getParameterNames,getParameterValues和getParameterMap等方法也可能需要覆盖。同理,排除xss情况,例如我们通过header中的token进行权限校验的时候,getHeaderNames方法也可能需要覆盖,否则可能在某些特定场景下相关的参数值无法进入filter管理,导致权限绕过缺陷。
相关推荐: 三娃为救爷爷大战蛇精,六娃偷看挖到了鹅厂的好多XSS
文章想要火,题目一定要长,要吸引人~ 大家好,我是子杰,团队里面人都叫我XSS小王子。又是一个愉快的一天,前几天看了有一篇关于QQ邮箱的Self-XSS的文章,发现这个洞跟我之前挖的某个洞漏源头是一样的,所以也发出来分享一下。 事情是这样的,某日月黑风高,我正…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论