spring boot实战之XSS过滤-HttpServletRequestWrapper的用法

过滤器和监听器 小海豚博客管理员 2020-06-27 19:49:40.0 645 0条

XSS攻击全称跨站脚本攻击,是为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS,XSS是一种在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。

你可以自己做个简单尝试:

  1. 在任何一个表单内,你输入一段简单的js代码:<script>for(var i=0;i<1000;i++){alert("弹死你"+i);}</script>,将其存入数据库;
  2. 在页面上一个div元素内直接展示第一步内存入的值,你会发现弹出框出现了;

以上XSS攻击只算一个小恶作剧,但如果这玩意被发到了网站的首页上,我估计老板一定会因为频繁的投诉而和你来场愉快的谈话…

以上两个示例仅仅算是恶作剧,恶意用户能做的更多,如获取用户信息,进行“网络钓鱼”攻击等。

应对XSS攻击的其中一个方式就是后端对输入内容进行过滤,输入内容里面的敏感信息直接过滤,如<script>标签等,以下来说明如何在spring boot项目内方便快捷的实现XSS过滤。

1、Jsoup组件

Jsoup使用标签白名单的机制用来进行防止XSS攻击, 假设白名单中只允许p标签存在, 此时在一段HTML代码中, 只能存在p标签 , 其他标签将会被清除只保留被标签所包裹的内容,因此使用Jsoup组件来进行内容过滤。

添加maven依赖:

  1. <!-- xss过滤组件 -->
  2. <dependency>
  3. <groupId>org.jsoup</groupId>
  4. <artifactId>jsoup</artifactId>
  5. <version>1.9.2</version>
  6. </dependency>

JsoupUtil提供基于Jsoup过滤非法标签的工具类:

  1. /**
  2. * xss非法标签过滤
  3. * {@link http://www.jianshu.com/p/32abc12a175a?nomobile=yes}
  4. */
  5. public class JsoupUtil {
  6. /**
  7. * 使用自带的basicWithImages 白名单
  8. * 允许的便签有a,b,blockquote,br,cite,code,dd,dl,dt,em,i,li,ol,p,pre,q,small,span,
  9. * strike,strong,sub,sup,u,ul,img
  10. * 以及a标签的href,img标签的src,align,alt,height,width,title属性
  11. */
  12. private static final Whitelist whitelist = Whitelist.basicWithImages();
  13. /** 配置过滤化参数,不对代码进行格式化 */
  14. private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false);
  15. static {
  16. /** 富文本编辑时一些样式是使用style来进行实现的
  17. 比如红色字体 style="color:red;"
  18. 所以需要给所有标签添加style属性*/
  19. whitelist.addAttributes(":all", "style");
  20. }
  21. public static String clean(String content) {
  22. return Jsoup.clean(content, "", whitelist, outputSettings);
  23. }
  24. public static void main(String[] args) throws FileNotFoundException, IOException {
  25. String text = "<a href=\"http://www.baidu.com/a\" onclick=\"alert(1);\">sss</a><script>alert(0);</script>sss";
  26. System.out.println(clean(text));
  27. }
  28. }

2、创建XssHttpServletRequestWrapper

这是实现XSS过滤的关键,在其内重写了getParameter,getParameterValues,getHeader等方法,对http请求内的参数进行了过滤。

  1. public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
  2. HttpServletRequest orgRequest = null;
  3. private boolean isIncludeRichText = false;
  4. public XssHttpServletRequestWrapper(HttpServletRequest request, boolean isIncludeRichText) {
  5. super(request);
  6. orgRequest = request;
  7. this.isIncludeRichText = isIncludeRichText;
  8. }
  9. /**
  10. * 覆盖getParameter方法,将参数名和参数值都做xss过滤。<br/>
  11. * 如果需要获得原始的值,则通过super.getParameterValues(name)来获取<br/>
  12. * getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
  13. */
  14. @Override
  15. public String getParameter(String name) {
  16. if(("content".equals(name) || name.endsWith("WithHtml")) && !isIncludeRichText){
  17. return super.getParameter(name);
  18. }
  19. name = JsoupUtil.clean(name);
  20. String value = super.getParameter(name);
  21. if (StringUtils.isNotBlank(value)) {
  22. value = JsoupUtil.clean(value);
  23. }
  24. return value;
  25. }
  26. @Override
  27. public String[] getParameterValues(String name) {
  28. String[] arr = super.getParameterValues(name);
  29. if(arr != null){
  30. for (int i=0;i<arr.length;i++) {
  31. arr[i] = JsoupUtil.clean(arr[i]);
  32. }
  33. }
  34. return arr;
  35. }
  36. /**
  37. * 覆盖getHeader方法,将参数名和参数值都做xss过滤。<br/>
  38. * 如果需要获得原始的值,则通过super.getHeaders(name)来获取<br/>
  39. * getHeaderNames 也可能需要覆盖
  40. */
  41. @Override
  42. public String getHeader(String name) {
  43. name = JsoupUtil.clean(name);
  44. String value = super.getHeader(name);
  45. if (StringUtils.isNotBlank(value)) {
  46. value = JsoupUtil.clean(value);
  47. }
  48. return value;
  49. }
  50. /**
  51. * 获取最原始的request
  52. *
  53. * @return
  54. */
  55. public HttpServletRequest getOrgRequest() {
  56. return orgRequest;
  57. }
  58. /**
  59. * 获取最原始的request的静态方法
  60. *
  61. * @return
  62. */
  63. public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
  64. if (req instanceof XssHttpServletRequestWrapper) {
  65. return ((XssHttpServletRequestWrapper) req).getOrgRequest();
  66. }
  67. return req;
  68. }
  69. }

3、创建XssFilter

XssFilter是过滤XSS请求的入口,在这里通过XssHttpServletRequestWrapper将HttpServletRequest进行了封装,filterChain.doFilter(xssRequest, response);保证了后续代码执行request.getParameter,request.getParameterValues,request.getHeader时调用的都是XssHttpServletRequestWrapper内重写的方法,获取到的参数是已经进行过标签过滤的内容,从而消除了敏感信息。

  1. /**
  2. * 拦截防止xss注入
  3. * 通过Jsoup过滤请求参数内的特定字符
  4. * @author yangwk
  5. */
  6. public class XssFilter implements Filter {
  7. private static Logger logger = LoggerFactory.getLogger(XssFilter.class);
  8. private static boolean IS_INCLUDE_RICH_TEXT = false;//是否过滤富文本内容
  9. public List<String> excludes = new ArrayList<String>();
  10. public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException,ServletException {
  11. if(logger.isDebugEnabled()){
  12. logger.debug("xss filter is open");
  13. }
  14. HttpServletRequest req = (HttpServletRequest) request;
  15. HttpServletResponse resp = (HttpServletResponse) response;
  16. if(handleExcludeURL(req, resp)){
  17. filterChain.doFilter(request, response);
  18. return;
  19. }
  20. XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request,IS_INCLUDE_RICH_TEXT);
  21. filterChain.doFilter(xssRequest, response);
  22. }
  23. private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {
  24. if (excludes == null || excludes.isEmpty()) {
  25. return false;
  26. }
  27. String url = request.getServletPath();
  28. for (String pattern : excludes) {
  29. Pattern p = Pattern.compile("^" + pattern);
  30. Matcher m = p.matcher(url);
  31. if (m.find()) {
  32. return true;
  33. }
  34. }
  35. return false;
  36. }
  37. @Override
  38. public void init(FilterConfig filterConfig) throws ServletException {
  39. if(logger.isDebugEnabled()){
  40. logger.debug("xss filter init~~~~~~~~~~~~");
  41. }
  42. String isIncludeRichText = filterConfig.getInitParameter("isIncludeRichText");
  43. if(StringUtils.isNotBlank(isIncludeRichText)){
  44. IS_INCLUDE_RICH_TEXT = BooleanUtils.toBoolean(isIncludeRichText);
  45. }
  46. String temp = filterConfig.getInitParameter("excludes");
  47. if (temp != null) {
  48. String[] url = temp.split(",");
  49. for (int i = 0; url != null && i < url.length; i++) {
  50. excludes.add(url[i]);
  51. }
  52. }
  53. }
  54. @Override
  55. public void destroy() {}
  56. }

4、注册XssFilter

通过java config的方式注册XSSFilter,使其生效。

  1. /**
  2. * xss过滤拦截器
  3. */
  4. @Bean
  5. public FilterRegistrationBean xssFilterRegistrationBean() {
  6. FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
  7. filterRegistrationBean.setFilter(new XssFilter());
  8. filterRegistrationBean.setOrder(1);
  9. filterRegistrationBean.setEnabled(true);
  10. filterRegistrationBean.addUrlPatterns("/*");
  11. Map<String, String> initParameters = Maps.newHashMap();
  12. initParameters.put("excludes", "/favicon.ico,/img/*,/js/*,/css/*");
  13. initParameters.put("isIncludeRichText", "true");
  14. filterRegistrationBean.setInitParameters(initParameters);
  15. return filterRegistrationBean;
  16. }

excludes用于配置不需要参数过滤的请求url
isIncludeRichText默认为true,主要用于设置富文本(项目内约束以content为名或以WithHtml结尾)内容是否需要过滤,该选项可根据公司具体情况调整,建议约束富文本编辑框支持的标签并开启改约束,减少安全隐患

暗锚,解决锚点偏移

文章评论

嘿,来试试登录吧!