随着web飞速的发展,XSS漏洞已经不容忽视,简单介绍一下XSS漏洞, 只要有用户输入的地方,就会出现XSS漏洞,例如在发表一篇帖子的时候,在其中加入脚本。
比如我在表单文本框中,输入
<script>alert(233)</script>
那么当这条消息存到数据库,再次在页面上访问获取数据的时候,就会弹出弹窗.
很多网站为了避免XSS的攻击,对用户的输入都采取了过滤,最常见的就是对<>转换成<以及>,经过转换以后<>虽然可在正确显示在页面上,但是已经不能构成代码语句了
前不久我写了一个JFinal的XSS,这个是在JBoot上面用到的
JFinal如何进行XSS攻击
现在又用到SpringMvc, 自然又得重新写一份SpringMvc的过滤方法
主要思路
- 建一个过滤器,过滤来自页面的请求
- 在过滤器中,将原本的request替换成我们自己包装好的request
- 我们写一个request的装饰类,里面重写获取参数的方法,对参数进行转义和替换
具体代码
1. 过滤器
package com.zgd.admin.common.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* 处理xss攻击的过滤器
* @author Admin
* 2018年5月23日16:12:26
*/
public class RequestFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 将request通过自定义的装饰类进行装饰
XssRequestWrapper xssRequest = new XssRequestWrapper((HttpServletRequest) request);
filterChain.doFilter(xssRequest, response);
}
}
2. reqeust包装类,重写getParameter()等方法,这里面对参数的转义和替换,根据实际需求更改
这里其实可以推荐一些html标签处理的框架
- 比如hutool的HtmlTool,可以轻松的转义标签,去除标签保留内容等。
hutool的官方文档 - 还有后面我写的SpringBoot处理xss攻击中使用的jsoup
- jsoup是解析html的框架,可以用
Jsoup.clean()
消除不安全的元素.
如:
public static void main(String[] args) {
String clean = Jsoup.clean("<a href='www.baidu.com>百度</a><script>alert(2333)</script><font color='red'>红色的</font>哈哈哈", Safelist.basic());
System.out.println("clean = " + clean); //<a rel="nofollow">红色的哈哈哈</a>
String relaxed = Jsoup.clean("<a href='www.baidu.com>百度</a><script>alert(2333)</script><font color='red'>红色的</font>哈哈哈", Safelist.relaxed());
System.out.println("relaxed = " + relaxed); //<a>红色的哈哈哈</a>
String simpleText = Jsoup.clean("<a href='www.baidu.com>百度</a><script>alert(2333)</script><font color='red'>红色的</font>哈哈哈", Safelist.simpleText());
System.out.println("simpleText = " + simpleText); //红色的哈哈哈
}
- 正则匹配
package com.zgd.api.gateway.handler;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class XssRequestWrapper extends HttpServletRequestWrapper {
private HttpServletRequest request;
public XssRequestWrapper(HttpServletRequest request) {
super(request);
this.request = request;
}
/**
* 重写getParameter方法
*/
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value == null) {
return null;
}
value = format(value);
return value;
}
/**
* 重写getParameterMap
*/
@Override
@SuppressWarnings("unchecked")
public Map<String, String[]> getParameterMap() {
HashMap<String, String[]> paramMap = (HashMap<String, String[]>) super.getParameterMap();
paramMap = (HashMap<String, String[]>) paramMap.clone();
for (Iterator iterator = paramMap.entrySet().iterator(); iterator.hasNext(); ) {
Map.Entry<String, String[]> entry = (Map.Entry<String, String[]>) iterator.next();
String [] values = entry.getValue();
for (int i = 0; i < values.length; i++) {
if(values[i] instanceof String){
values[i] = format(values[i]);
}
}
entry.setValue(values);
}
return paramMap;
}
/**
* 重写getParameterValues
*/
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = format(values[i]);
}
return encodedValues;
}
/**
* 重写getHeader
*/
@Override
public String getHeader(String name) {
// TODO Auto-generated method stub
return format(super.getHeader(name));
}
public String filter(String message) {
if (message == null)
return (null);
message = format(message);
return message;
}
/**
* @desc 统一处理特殊字符的方法,替换掉sql和js的特殊字符
* @param name 要替换的字符
*/
private String format(String name) {
return xssEncode(name);
}
/**
* 将容易引起xss & sql漏洞的半角字符直接替换成全角字符
*
* @param s
* @return
*/
private static String xssEncode(String s) {
if (s == null || s.isEmpty()) {
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跨脚本攻击(替换,根据实际情况调整)
*/
public static String stripXSSAndSql(String value) {
if (value != null) {
// NOTE: It's highly recommended to use the ESAPI library and
// uncomment the following line to
// avoid encoded attacks.
// value = ESAPI.encoder().canonicalize(value);
// Avoid null characters
/** value = value.replaceAll("", "");***/
// Avoid anything between script tags
Pattern scriptPattern = Pattern.compile("<[\r\n| | ]*script[\r\n| | ]*>(.*?)</[\r\n| | ]*script[\r\n| | ]*>", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid anything in a src="http://www.yihaomen.com/article/java/..." type of e-xpression
scriptPattern = Pattern.compile("src[\r\n| | ]*=[\r\n| | ]*[\\\"|\\\'](.*?)[\\\"|\\\']", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Remove any lonesome </script> tag
scriptPattern = Pattern.compile("</[\r\n| | ]*script[\r\n| | ]*>", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Remove any lonesome <script ...> tag
scriptPattern = Pattern.compile("<[\r\n| | ]*script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid eval(...) expressions
scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid e-xpression(...) expressions
scriptPattern = Pattern.compile("e-xpression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid javascript:... expressions
scriptPattern = Pattern.compile("javascript[\r\n| | ]*:[\r\n| | ]*", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid vbscript:... expressions
scriptPattern = Pattern.compile("vbscript[\r\n| | ]*:[\r\n| | ]*", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid οnlοad= expressions
scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
}
return value;
}
}
3. 在web.xml中把自己写的过滤器,放在合适的地方,可以考虑放在编码过滤器后面
<!-- xss注入过滤器 -->
<filter>
<filter-name>xssRequestFilter</filter-name>
<filter-class>com.zgd.admin.common.filter.RequestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>xssRequestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
4. 接下来就可以启动项目看看效果了
我们输入
<script>alert(2333)</script><font color='red'>红色的</font>哈哈哈
结果