免费发布信息
当前位置:APP交易 > 热点资讯 > app交易 >  记一次失效的XSS过滤器修复

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

发表时间:2021-07-09 17:05:06  来源:红帽社区  浏览:次   【】【】【
红帽社区是一个垂直网络安全社区,融合“红帽先锋”正能量精神,每日分享最新安全资讯,提供安全问答、靶场、众测、漏洞库等功能,是网络安全爱好者学习、交流的优质社区。

奇怪的XSS过滤器

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

  一开始以为过滤器的匹配接口路径存在问题,查看代码发现开发是通过写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参数的值设置为<>alert(1),访问测试接口debug查看在经过过滤器处理后的值:

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

复盘

  刚刚提到,问题接口都使用了@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参数的值设置为<>alert(1),访问测试接口debug查看在经过过滤器处理后的值:

  这里输入的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属性的值设置为<>alert(1),访问测试接口debug查看在经过过滤器处理后的值:

  这里输入的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.springwork.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参数的值设置为<>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过滤器

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

@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注解进行参数绑定,测试接口如下:

@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对象绑定的方式,测试接口如下:

@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属性的值设置为<>alert(1),访问测试接口debug查看在经过过滤器处理后的值:
图片.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参数的值设置为<>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.springwork.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参数的值设置为<>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管理,导致权限绕过缺陷。


责任编辑:
声明:本平台发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。

德品

1377 678 6470