xss过滤器无法处理ajax请求_原创 | 记一次失效的XSS过滤器修复

6f51fd6c2f0ac79a495967389e647e99.gif

点击上方蓝字关注我们

6f51fd6c2f0ac79a495967389e647e99.gif 82b803a0a82843e25d8b5a4737e87cd8.png

奇怪的XSS过滤器

82b803a0a82843e25d8b5a4737e87cd8.png

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

fdd27a1585cdc09a6c08d8bbc126dcb9.png

一开始以为过滤器的匹配接口路径存在问题,查看代码发现开发是通过写HttpServletRequestWrapper的Method对所有的request进行处理的:

相关代码如下,系统使用SSM开发:
首先定义一个filter,通过重写HttpServletRequestWrapper的方法修改request参数:

@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过滤:

public class SecHttpRequestWrapper extends HttpServletRequestWrapper {  private HttpServletRequest request;  public SecHttpRequestWrapper(HttpServletRequest request){    super(request);    this.request = request;  }  /**   * 覆盖getParameter方法,将参数名和参数值都做xss过滤。   */  @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跨站脚本攻击的困扰了。
例如如下测试接口:

@RequestMapping(value = "/servlet_req_param",method = { RequestMethod.POST, RequestMethod.GET })    public String req_param(HttpServletRequest request){        return request.getParameter("content");    }
将content参数的值设置为 2e2bf014d40f5afc6042e2d7d16544b6.png

可以看到输入的恶意xsspayload的确是经过相关的编码操作的。
这么梳理下来好像的确是这么回事,但是结合测试,的确是过滤器失效了部分接口仍可进行xss利用。看一下仍可进行xss利用的接口,发现一个问题就是都使用了@RequestParam注解和POJO对象绑定的方式进行传参。

82b803a0a82843e25d8b5a4737e87cd8.png

复盘

82b803a0a82843e25d8b5a4737e87cd8.png

刚刚提到,问题接口都使用了@RequestParam注解和POJO对象绑定的方式进行传参。

在实际开发中,软件框架可以自动将HTTP请求参数绑定到程序代码变量或者对象中,从而使得更易开发。在SpringMVC中,提交请求的数据是通过方法形参来接收的。从客户端请求的key/value数据,经过参数绑定,将key/value数据绑定到Controller的形参上,然后在Controller就可以直接使用该形参。

那么就来看看@RequestParam注解和POJO对象绑定的方式跟过滤器之间有什么联系好了。
使用@RequestParam注解进行参数绑定,测试接口如下:
 @RequestMapping(value = "/req_param2",method = { RequestMethod.POST, RequestMethod.GET })    public String req_param(ModelMap modelmap, @RequestParam("content") String content) {        return content;    }

将content参数的值设置为

334604cb1fbf253c48eea8b1f889597d.png这里输入的content经过filter后原样输出了,并没有经过xssEncode()方法进行过滤/编码操作。

再试试POJO对象绑定的方式,测试接口如下:

@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类:

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属性的值设置为

78f422f045682909f3bddc0b5adf0a64.png

这里输入的User属性经过filter后同样也原样输出了,并没有经过xssEncode()方法进行过滤/编码操作。

再试试基本String类型的参数绑定,测试接口如下:

@RequestMapping(value = "/req_param1",method = { RequestMethod.POST, RequestMethod.GET })    public String req_var(ModelMap modelmap, String content) {        return content;    }

同样的,将content参数的值设置为

123ee6c9bf8cef2f3a21d4041427a9c9.png以上的结果都跟我们的预期相悖,出现了Filter无法覆盖的场景。

再回头看一下我们的filter,我们是通过重写HttpServletRequestWrapper的getParameter()方法进行参数编码/过滤的,也就是说:

springmvc直接绑定pojo或者@RequestParam(基本数据类型绑定)没法在HttpServletRequestWrapper的getparameter方法进行过滤操作。

82b803a0a82843e25d8b5a4737e87cd8.png

到底漏了什么

82b803a0a82843e25d8b5a4737e87cd8.png

猜测很大可能是跟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中看到如下代码:

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:
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参数的值设置为

135b9acfbaca51d3b388b5af25fba7ae.png

可以看到成功在getParameterValues拦截了我们的输入并且在xssEncode()方法进行了过滤/编码操作。印证了前面的想法。

同理,POJO绑定猜测也是通过getParameterValues进行参数值的获取,这里将User属性的值设置为

f11ed6496d601fe5ec870f9aacfebe15.png可以看到成功在getParameterValues拦截了我们的输入并且在xssEncode()方法进行了过滤/编码操作,也就是说POJO绑定猜测也是通过getParameterValues进行参数值的获取的。

也就是说,springmvc直接绑定pojo或者@RequestParam(基本数据类型绑定)可以在HttpServletRequestWrapper的getparameterValues方法进行过滤操作。另外搭建了一下环境测试了一下Springboot,也是同样的效果。

82b803a0a82843e25d8b5a4737e87cd8.png总结与延伸 82b803a0a82843e25d8b5a4737e87cd8.png

filter的实际开发设计时需要关注的点很多,一不注意很可能就会导致我们的安全措施被绕过。在通过重写HttpServletRequestWrapper的Method进行filter设计的时候,要额外关注上述的细节。根据上述场景,进一步引申,可能getParameterNames,getParameterValues和getParameterMap等方法也可能需要覆盖。同理,排除xss情况,例如我们通过header中的token进行权限校验的时候,getHeaderNames方法也可能需要覆盖,否则可能在某些特定场景下相关的参数值无法进入filter管理,导致权限绕过缺陷。

e5288a68172d7033521f4138a7289ab2.gif第一时间给你最新技术干货在这里,探索技术与热爱! 8d3e7c5f3fc69bbff8a4eba93039c27f.png e5288a68172d7033521f4138a7289ab2.gif点击此处“阅读全文”查看更多内容
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值