SpringBoot过滤XSS脚本攻击
Posted 好记性不如烂笔头=》
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot过滤XSS脚本攻击相关的知识,希望对你有一定的参考价值。
XSS攻击是什么
XSS攻击全称跨站脚本攻击,是为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS,XSS是一种在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。
简而言之,就是作恶用户通过表单提交一些前端代码,如果不做处理的话,这些前端代码将会在展示的时候被浏览器执行。
如何避免XSS攻击
这里我根据个人经验做一个总结,可能经验也有不足之处。我个人解决XSS攻击是通过后端转译的办法来解决的。在实际项目中,react、vue等前后端完全分离的框架似乎已经帮我们处理了XSS脚本,这个本人对于前端略懂皮毛而已,这里就不做探讨了。下面主要实现以后端做XSS过滤。
代码实现
对于过滤XSS脚本的代码,通过搜索引擎可以搜索到很多,但似乎都不是那么全面。基本上都是只能过滤querystring类型的入参,而不能过滤json类型的入参。其实,在现在的开发中,更多的是使用json类型做数据交互。下面就直接贴代码了:
XssAndSqlHttpServletRequestWrapper.java
package com.loger.filter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
* @author wbs
* 防止XSS攻击
*/
public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrapper {
private HttpServletRequest request;
public XssAndSqlHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
this.request = request;
}
@Override
public String getParameter(String name) {
String value = request.getParameter(name);
if (!StringUtils.isEmpty(value)) {
value = StringEscapeUtils.escapehtml4(value);
}
return value;
}
@Override
public String[] getParameterValues(String name) {
String[] parameterValues = super.getParameterValues(name);
if (parameterValues == null) {
return null;
}
for (int i = 0; i < parameterValues.length; i++) {
String value = parameterValues[i];
//这个过滤xss攻击的工具类,现在是借助第三方插件使用的。 也可以自己写一个工具类 比如下面的
XssUtil
parameterValues[i] = StringEscapeUtils.escapeHtml4(value);
//自定义工具类
//
parameterValues[i] = XssUtil.xssEncode(parameterValues[i]);
} return parameterValues; } }
简单讲解下,这里重写了两个方法:getParameter和getParameterValues,getParameter方法是直接通过request获得querystring类型的入参调用的方法。如果是通过springMVC注解类型来获得参数的话,走的是getParameterValues的方法。
StringEscapeUtils.escapeHtml4这个方法来自Apache的工具类,maven坐标如下:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.4</version>
</dependency>
//如果使用的是上面黄色的自定义工具类,来过滤xss 关键字,就需要导入下面XssUtils工具类,如果不使用的话下面这个工具类,就直接跳过不需要管。 但是有些人说为啥有第三方工具类,还要使用自定义工具类呢,
应为使用第三方插件时,进行代码扫描时(我们公司项目上线需要进行代码扫描)会有问题(这个问题不影响代码使用),所以我用的是自定义类。总之这两种 工具类都可以用,不影响功能的实现。
/**
* @Author wbs
* @Description Web防火墙工具类
*/
public class XssUtil {
public static final String REPLACE_STRING = "*";
private XssUtil() {
}
/**
* xss校验
* @param s
* @return
*/
public static String xssEncode(String s) {
if (StringUtils.isEmpty(s)) {
return s;
} else {
s = stripXSSAndSql(s);
}
StringBuilder sb = new StringBuilder(s.length() + 16);
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case ‘>‘:
sb.append(">");// 转义大于号
break;
case ‘<‘:
sb.append("<");// 转义小于号
break;
case ‘‘‘:
sb.append("'");// 转义单引号
break;
case ‘"‘:
sb.append(""");// 转义双引号
break;
case ‘&‘:
sb.append("&");// 转义&
break;
case ‘#‘:
sb.append("#");// 转义#
break;
default:
sb.append(c);
break;
}
}
return sb.toString();
}
/**
* xss校验
* @param value
* @return
*/
public static String stripXSSAndSql(String value) {
if (StringUtils.isNotEmpty(value)) {
// Avoid null characters
value = value.replaceAll(" ", REPLACE_STRING);
// Avoid anything between script tags
Pattern scriptPattern = Pattern.compile("<[ | | ]*script[ | | ]*>(.*?)</[ | | ]*script[ | | ]*>", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll(REPLACE_STRING);
// Avoid anything in a src="http://www.yihaomen.com/article/java/..." type of e-xpression
scriptPattern = Pattern.compile("src[ | | ]*=[ | | ]*[\"|\‘](.*?)[\"|\‘]", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll(REPLACE_STRING);
// Remove any lonesome </script> tag
scriptPattern = Pattern.compile("</[ | | ]*script[ | | ]*>", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll(REPLACE_STRING);
// Remove any lonesome <script ...> tag
scriptPattern = Pattern.compile("<[ | | ]*script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll(REPLACE_STRING);
// Avoid eval(...) expressions
scriptPattern = Pattern.compile("eval\((.*?)\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll(REPLACE_STRING);
// Avoid e-xpression(...) expressions
scriptPattern = Pattern.compile("e-xpression\((.*?)\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll(REPLACE_STRING);
// Avoid javascript:... expressions
scriptPattern = Pattern.compile("javascript[ | | ]*:[ | | ]*", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll(REPLACE_STRING);
// Avoid vbscript:... expressions
scriptPattern = Pattern.compile("vbscript[ | | ]*:[ | | ]*", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll(REPLACE_STRING);
// Avoid onload= expressions
scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll(REPLACE_STRING);
// Avoid /r /n:... expressions
scriptPattern = Pattern.compile(""\\s*|\t|\r|\n"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
}
return value;
}
}
过滤的代码写完了,下面就是在一个filter中应用该代码。
XssFilter.java
package com.loger.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @author wbs
*/
@WebFilter(filterName = "xssFilter", urlPatterns = "/*", asyncSupported = true)
@Component public class XssFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; XssAndSqlHttpServletRequestWrapper xssRequestWrapper = new XssAndSqlHttpServletRequestWrapper(req); chain.doFilter(xssRequestWrapper, response); } @Override public void destroy() { } /** * 过滤json类型的 * @param builder * @return */ @Bean @Primary public ObjectMapper xssObjectMapper(Jackson2ObjectMapperBuilder builder) { //解析器 ObjectMapper objectMapper = builder.createXmlMapper(false).build(); //注册xss解析器 SimpleModule xssModule = new SimpleModule("XssStringJsonSerializer"); xssModule.addSerializer(new XssStringJsonSerializer()); objectMapper.registerModule(xssModule); //返回 return objectMapper; } }
就这样,过滤querystring类型的代码已经完成(xssObjectMapper这个是后面过滤json类型才用到的)。下面来实现过滤json类型的代码:
XssStringJsonSerializer.java
package com.loger.filter;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.apache.commons.text.StringEscapeUtils;
import java.io.IOException;
public class XssStringJsonSerializer extends JsonSerializer<String> {
@Override
public Class<String> handledType() {
return String.class;
}
@Override
public void serialize(String value, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
if (value != null) {
String encodedValue = StringEscapeUtils.escapeHtml4(value);
jsonGenerator.writeString(encodedValue);
}
}
}
这里通过修改SpringMVC的json序列化来达到过滤xss的目的的。其实也可以通过第一种方法,重写getInputStream方法来实现,但由于得到的是ServletInputStream,不太好处理。(通过json类型传参会走getInputStream方法,通过重写该方法打印输出可以证明)
下面可以通过几个例子验证下是否成功:
简单写一个controller
TestController.java
package com.loger.controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
* @author wbs
* Description :
*/
@RestController
@RequestMapping(value = "/test")
public class TestController {
@PostMapping(value = "/xss")
public Object test(String name) {
System.out.println(name);
return name;
}
@PostMapping(value = "/json")
public Object testJSON(@RequestBody Param param) {
return param;
}
@GetMapping(value = "/query")
public Object testQuery(String q){
return q;
}
@PostMapping(value = "/upload")
public Object upload(MultipartFile file){
System.out.println(file.getOriginalFilename());
return "OK";
}
}
下面通过postman测试下效果: