一、背景
介绍:XSS(Cross Site Scripting)指的是用户注入恶意的代码,浏览器和服务器没有对用户的输入进行过滤,导致用户注入的脚本嵌入到了页面中。由于浏览器无法识别这些恶意代码正常解析执行,攻击者的恶意操作被成功执行。
预防XSS攻击不仅是前端开发人员要做的事情,也是是后端开发人员要做的事情。本篇章节是针对后端开发人员怎么预防XSS攻击。
常见的XSS攻击分为三种:
- 反射型: 通过在请求地址上加上恶心的HTML代码。
- dom型: 通过一些api向网站注入一些恶心的HTML代码。
- 持久型: 攻击者通过把代码提交到后台数据库中;当用户下次打开的时候就会从后台接收这些恶意的代码。
防范:
- 反射型: 前端通过转义来进行防范以及过滤
- dom型:前端通过转义来进行防范以及过滤
- 持久型:服务端通过转义存储进行防范
对于反射型与dom型的XSS攻击,需要前端做转义。本篇博客主要讲解后端的转义处理。
二、目的
- 针对输入包含dom敏感的数据进行过滤
- 针对输入包含sql相关的敏感信息进行过滤
三、实现方式
使用Filter过滤器
使用Filter,将所有的敏感信息替换成空字符串
public class XSSPreventionFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
XSSRequestWrapper wrapper = new XSSRequestWrapper((HttpServletRequest) request);
chain.doFilter(wrapper, response);
}
class XSSRequestWrapper extends HttpServletRequestWrapper {
private Map<String, String[]> sanitizedQueryString;
XSSRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
String parameter = null;
String[] vals = getParameterMap().get(name);
if (vals != null && vals.length > 0) {
parameter = vals[0];
}
return parameter;
}
@Override
public String[] getParameterValues(String name) {
return getParameterMap().get(name);
}
@Override
public Enumeration<String> getParameterNames() {
return Collections.enumeration(getParameterMap().keySet());
}
@Override
public Map<String, String[]> getParameterMap() {
if (sanitizedQueryString == null) {
Map<String, String[]> res = new HashMap<>();
Map<String, String[]> originalQueryString = super.getParameterMap();
if (originalQueryString != null) {
for (String key : originalQueryString.keySet()) {
String[] rawVals = originalQueryString.get(key);
String[] snzVals = new String[rawVals.length];
for (int i = 0; i < rawVals.length; i++) {
snzVals[i] = stripXSS(rawVals[i]);
}
res.put(stripXSS(key), snzVals);
}
}
sanitizedQueryString = res;
}
return sanitizedQueryString;
}
/**
* 从字符串中删除所有潜在的恶意字符
*
* @param value the raw string
* @return the sanitized string
*/
private String stripXSS(String value) {
String cleanValue = null;
if (value != null) {
cleanValue = Normalizer.normalize(value, Normalizer.Form.NFD);
// 删除空字符
cleanValue = cleanValue.replaceAll("\0", "");
// 删除<script></script>标签
Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);
cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
// 删除src='...'
scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*'(.*?)'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
// 删除</script>标签
scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE);
cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
// 删除<script ...>标签
scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
// 删除eval(...)表达式
scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
// 删除expression(...)表达式
scriptPattern = Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
// 删除javascript:...表达式
scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE);
cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
// 删除vbscript:...表达式
scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE);
cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
// 删除onload= 表达式
scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
// 删除 sql ' 和 ; 字符串
scriptPattern = Pattern.compile("[';]", Pattern.CASE_INSENSITIVE);
cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
// 删除 sql -- 字符
scriptPattern = Pattern.compile("--", Pattern.CASE_INSENSITIVE);
cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
}
return cleanValue;
}
}
}
配置Filter过滤器
配置xss防注入filter bean
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean frb = new FilterRegistrationBean();
frb.setFilter(new XSSPreventionFilter());
frb.setOrder(1);
frb.addUrlPatterns("/*");
return frb;
}
}
以上完成,搞定。