背景:
不久前公司有接了一个国土的项目,虽然是内部小项目,但可能因为是zhengfu项目,竟然找软件测试公司去大概测了一下。。。然后出现了以下三种问题:sql注入,XSS攻击,接口访问频率。下面是解决XSS攻击。
研究:
一开始的想法是,弄个过滤器把那些关键字过滤掉不就好了,例如script、eval、document等等,确实网上也有这些例子。可参考此博客https://www.cnblogs.com/myyBlog/p/8890365.html
但是人家可是请专业的软件测试工程师测试的,我这么混过去肯定是不行滴,上面的主要只是简单地过滤掉用户的输入参数,而且过滤得非常的不全面,可能还有多方面的跨站脚本攻击呢?然后继续的百度找找有没有此方面的开源项目。结果真的找到了一个,就是AntiSamy,OWASP的一个开源项目,通过对用户输入的 HTML / CSS / JavaScript 等内容进行检验和清理,确保输入符合应用规范。AntiSamy被广泛应用于Web服务对存储型和反射型XSS的防御中。这个就相当的强大了啊。
方案:
参考上面的博客的做法:自定义一个过滤器XssFilter,拦截所有路径。在doFilter中使用HttpServletRequestWrapper来包装一下HttpServletRequest(进行XSS清洗达到防御XSS攻击),最后交给filterChain往下执行。嗯,就这么大概了,网友真强大~
开工:
1、引入Maven依赖:
org.owasp.antisamy
antisamy
1.5.5
2、策略文件:
Maven下载下来的依赖里面是带有策略文件,之前试过前一点的版本例如1.5.3好像是没有的。下面我使用的策略文件将是antisamy-ebay.xml,毕竟eBay是当下最流行的在线拍卖网站之一,防御策略肯定是非常好的。关于AntiSamy的策略文件的详解大家可以去搜一下,毕竟可能以后要改里面的处理规则呢。
3、怎么使用AntiSamy:
想当的简单,首先要指定策略文件生成Policy,然后新建AntiSamy,再对需要进行XSS清洗的内容进行扫面即可。
String xsshtml = "hyf";
Policy policy = Policy.getInstance("antiSamyPath");
AntiSamy antiSamy = new AntiSamy();
CleanRequests cr = antiSamy.scan(xsshtml,policy);
xsshtml = cr.getCleanHTML(); //清洗完的
4、创建XssFilter实现Filter接口然后重写doFilter方法:
因为我们对用户的每次请求都进行拦截清洗,清洗完才能真正地调用Controller的方法,所以我们用到的是过滤器而不是拦截器。因为Filter是一个典型的过滤链,可以用来对 HttpServletRequest 进行预处理,或者是对 HttpServlerResponse 进行后处理。
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
*
*@author howinfun
*@date 2018/10/23
*@company DM
*@version 1.0
*/
public class XssFilter implements Filter {
private FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
/* filterChain.doFilter(new XssHttpServletRequestWrapper2(request),servletResponse);*/
filterChain.doFilter(new XssHttpServletRequestWrapper(request),servletResponse);
}
@Override
public void destroy() {
this.filterConfig = null;
}
}
5、新建一个自定义 HttpServletRequest 包装类 XssHttpServletRequestWrapper :
继承 HttpServletRequestWrapper 类,对getParameter( String param)、getParameterValues( String param)以及 getHeader( String param) 等方法进行重写,都将进行一遍全方位清洗。
import org.apache.commons.lang.StringEscapeUtils;
import org.jeecgframework.web.appconfig.util.StringUtils;
import org.owasp.validator.html.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Iterator;
import java.util.Map;
/**
* @desc 基于AntiSamy的XSS防御
* @author howinfun
* @date 2018/10/23
* @company DM
* @version 1.0
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
//AntiSamy使用的策略文件
private static Policy policy = null;
static {
String antiSamyPath = XssHttpServletRequestWrapper.class.getClassLoader().getResource("antisamy-ebay.xml").getFile();
System.out.println("policy_filepath:"+antiSamyPath);
if(antiSamyPath.startsWith("file")){
antiSamyPath = antiSamyPath.substring(6);
}
try {
policy = Policy.getInstance(antiSamyPath);
} catch (PolicyException e) {
e.printStackTrace();
}
}
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
/**
* @desc Header为空直接返回,不然进行XSS清洗
* @author howinfun
* @date 2018/10/24
*
*/
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
if(StringUtils.isEmpty(value)){
return value;
}
else{
String newValue = cleanXSS(value);
return newValue;
}
}
/**
* @desc Parameter为空直接返回,不然进行XSS清洗
* @author howinfun
* @date 2018/10/24
*
*/
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if(StringUtils.isEmpty(value)){
return value;
}
else{
value = cleanXSS(value);
return value;
}
}
/**
* @desc 对用户输入的参数值进行XSS清洗
* @author howinfun
* @date 2018/10/24
*
*/
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values != null) {
int length = values.length;
String[] escapseValues = new String[length];
for (int i = 0; i < length; i++) {
escapseValues[i] = cleanXSS(values[i]);
}
return escapseValues;
}
return super.getParameterValues(name);
}
@SuppressWarnings("rawtypes")
public Map getParameterMap(){
Map request_map = super.getParameterMap();
Iterator iterator = request_map.entrySet().iterator();
System.out.println("request_map"+request_map.size());
while(iterator.hasNext()){
Map.Entry me = (Map.Entry)iterator.next();
//System.out.println(me.getKey()+":");
String[] values = (String[])me.getValue();
for(int i = 0 ; i < values.length ; i++){
System.out.println(values[i]);
values[i] = cleanXSS(values[i]);
}
}
return request_map;
}
/**
* @desc AntiSamy清洗数据
* @author howinfun
* @date 2018/10/24
*
*/
private String cleanXSS(String taintedHTML) {
try{
AntiSamy antiSamy = new AntiSamy();
CleanResults cr = antiSamy.scan(taintedHTML, policy);//扫描
taintedHTML = cr.getCleanHTML();//获取清洗后的结果
return taintedHTML;
}catch( ScanException e) {
e.printStackTrace();
}catch( PolicyException e) {
e.printStackTrace();
}
return taintedHTML;
}
}
6、在web.xml对新建的Filter进行注册。
XSSFilter
org.jeecgframework.core.filter.XssFilter
XSSFilter
/*
7、接下来进行测试:
简单的测试是否能过滤掉script标签及里面的内容。
8、继续测试:
我继续测试其他接口,测试了一个之前自己做的通用API,发现传回来的JSON数据出现了问题,原来的双引号被转换为"。还有网友发现 也会被转成乱码了。所以我们必须在清洗完后进行进一步处理,将这两个问题给自己解决掉。
/**
* @desc AntiSamy清洗数据
* @author howinfun
* @date 2018/10/24
*
*/
private String cleanXSS(String taintedHTML) {
try{
AntiSamy antiSamy = new AntiSamy();
final CleanResults cr = antiSamy.scan(taintedHTML,policy);
//AntiSamy会把“ ”转换成乱码,把双引号转换成""" 先将 的乱码替换为空,双引号的乱码替换为双引号
String str = StringEscapeUtils.unescapeHtml(cr.getCleanHTML());
str = str.replaceAll(antiSamy.scan(" ",policy).getCleanHTML(),"");
str = str.replaceAll(antiSamy.scan("\"",policy).getCleanHTML(),"\"");
return str;
}catch( ScanException | PolicyException e) {
e.printStackTrace();
}
return taintedHTML;
}
到此,全部结束~