记一次失效的XSS过滤器修复

  • A+

奇怪的XSS过滤器

  一般在实际Web开发中,我们常常需要过滤/编码页面传递给后台的特殊字符,防止xss跨站脚本攻击。使用过滤器Filter可以很好的完成这个功能。前段时间某项目的研发使用过滤器进行XSS过滤后,进行复测,发现一个奇怪的现象,原本网站全局的xss,变成了只有部分接口存在xss

图片.png
  一开始以为过滤器的匹配接口路径存在问题,查看代码发现开发是通过写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处理
}

}
&emsp;&emsp;通过上面的操作,前端传递过来的内容就可以在Controller中使用request.getParameter("xxx")的形式获取到之后,然后进行xss编码处理,从而在进行view层展示或者写入数据库前保证系统免受xss跨站脚本攻击的困扰了。
&emsp;&emsp;例如如下测试接口:
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查看在经过过滤器处理后的值:

图片.png
  可以看到输入的恶意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查看在经过过滤器处理后的值:

图片.png
  这里输入的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;

}
&emsp;&emsp;跟前面的一样,将User属性的值设置为<script>alert(1)</script>,访问测试接口debug查看在经过过滤器处理后的值:
![图片.png](/img/sin/M00/00/19/wKg0C16EiUWAHwfFAABc3kn79Go452.png)
&emsp;&emsp;这里输入的User属性经过filter后同样也原样输出了,并没有经过xssEncode()方法进行过滤/编码操作。
&emsp;&emsp;再试试基本String类型的参数绑定,测试接口如下:
java
@RequestMapping(value = "/req_param1",method = { RequestMethod.POST, RequestMethod.GET })
public String req_var(ModelMap modelmap, String content) {
return content;
}
```
  同样的,将content参数的值设置为alert(1),访问测试接口debug查看在经过过滤器处理后的值:

图片.png
  以上的结果都跟我们的预期相悖,出现了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查看在经过过滤器处理后的值:

图片.png
  可以看到成功在getParameterValues拦截了我们的输入并且在xssEncode()方法进行了过滤/编码操作。印证了前面的想法。
  同理,POJO绑定猜测也是通过getParameterValues进行参数值的获取,这里将User属性的值设置为alert(1),访问测试接口debug查看在经过过滤器处理后的值:

图片.png
  可以看到成功在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的文章,发现这个洞跟我之前挖的某个洞漏源头是一样的,所以也发出来分享一下。 事情是这样的,某日月黑风高,我正…