XSS跨站攻击
跨站脚本攻击(Cross Site Scripting),为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。
AntiSamy介绍
OWASP AntiSamy项目可以有好几种定义。从技术角度看,它是一个可确保用户输入的HTML/CSS符合应用规范的API。也可以这么说,它是个确保用户无法在HTML中提交恶意代码的API,而这些恶意代码通常被输入到个人资料、评论等会被服务端存储的数据中。在Web应用程序中,“恶意代码”通常是指 Javascript。同时层叠样式表(CSS)在调用Javascript引擎的时候也会被认为是恶意代码。当然在很多情况下,一些“正常”的HTML 和CSS也会被用于恶意的目的,所以我们也会对此予以处理。
使用AntiSamy防御XSS攻击
1. maven 工程引入AntiSamy
<dependency>
<groupId>org.owasp.antisamy</groupId>
<artifactId>antisamy</artifactId>
<version>1.5.3</version>
</dependency>
2. 策略文件
antisamy-slashdot.xml
Slashdot (http://www.slashdot.org/)是一个提供技术新闻的网站,它允许用户用有限的HTML格式的内容匿名回帖。
Slashdot不仅仅是目前同类中最酷的网站之一,而且同时也曾是最容易被成功攻击的网站之一。更不幸的是,导致大部分用户遭受攻击的原由是臭名昭着的goatse.cx 图片(请你不要刻意去看)。
Slashdot的安全策略非常严格:用户只能提交下列的html标签:<b>, <u>, <i>, <a>, <blockquote>,并且还不支持CSS.因此我们创建了这样的策略文件来实现类似的功能。它允许所有文本格式的标签来直接修饰字体、颜色或者强调作用。
众所周知,eBay (http://www.ebay.com/)是当下最流行的在线拍卖网站之一。它是一个面向公众的站点,因此它允许任何人发布一系列富HTML的内容。我们对eBay成为一些复杂XSS攻击的目标,并对攻击者充满吸引力丝毫不感到奇怪。由于eBay允许输入的内容列表包含了比Slashdot更多的富文本内容,所以它的受攻击面也要大得多。
antisamy-myspace.xml
MySpace (http://www.myspace.com/)是最流行的一个社交网站之一。用户允许提交几乎所有的他们想用的HTML和CSS,只要不包含JavaScript。MySpace现在用一个黑名单来验证用户输入的HTML,这就是为什么它曾受到Samy蠕虫攻击(http://namb.la/)的原因。Samy蠕虫攻击利用了一个本应该列入黑名单的单词(eval)来进行组合碎片攻击的,其实这也是AntiSamy立项的原因。
antisamy-anythinggoes.xml
如果你想允许所有有效的HTML和CSS元素输入(但能拒绝JavaScript或跟CSS相关的网络钓鱼攻击),你可以使用这个策略文件。其实即使MySpace也没有这么疯狂。然而,它确实提供了一个很好的参考,因为它包含了对于每个元素的基本规则,所以你在裁剪其它策略文件的时候可以把它作为一个知识库。
antisamy-tinymce.xml
只允许文本格式通过,相对较安全
antisamy.xml
默认规则,允许大部分HTML通过
3. AntiSamy的使用
策略文件加载
private static Policy policy = null;
static{
if (policy == null) {
try {
policy = Policy.getInstance("/WEB-INF/classes/antisamy-slashdot.xml");
} catch (PolicyException e) {
log.warn("XssFilter - static policy instance error. PolicyException=" , e);
}
}
}
XSS过滤核心代码
/**
* 过滤xss攻击违规输入, 2017, zhujq
* @param value 待过滤字符串
* @return
*/
public String xssClean(String value){
log.info("xssClean() - value=" + value + ", start. ");
AntiSamy antiSamy = new AntiSamy();
try {
//Policy policy = Policy.getInstance(APIGlobal.XSS_ANTISAMY_FILE_PATH);
final CleanResults cr = antiSamy.scan(value, policy);
String cleanValue = cr.getCleanHTML();
log.info("xssClean() - value=" + value + ", cleanValue=" + cleanValue);
return cleanValue;
} catch (PolicyException e) {
log.warn("xssClean() - value=" + value + ", PolicyException=", e);
} catch (ScanException e) {
log.warn("xssClean() - value=" + value + ", ScanException=", e);
} catch (Exception e) {
log.warn("xssClean() - value=" + value + ", Exception=", e);
}
return value;
}
WEB过滤器
/**
* Copyright (c) 2017 21CN.COM . All rights reserved.
*
* Description: edrive-api-3.0
*
* Modified log:
* ------------------------------------------------------
* Ver. Date Author Description
* ------------------------------------------------------
* 1.0 2017-11-29 jinQiang.zhu created.
*/
package com.cn21.edrive.product.api.rest.test;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
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.HttpServletRequestWrapper;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.owasp.validator.html.AntiSamy;
import org.owasp.validator.html.CleanResults;
import org.owasp.validator.html.Policy;
import org.owasp.validator.html.PolicyException;
import org.owasp.validator.html.ScanException;
import com.cn21.edrive.adaptor.weibo.util.ReadPropertiesFile;
import com.cn21.edrive.product.api.rest.APIGlobal;
/**
* @author jinQiang.zhu
*
*/
public class XssFilter implements Filter{
private static final Logger log = Logger.getLogger(XssFilter.class);
private FilterConfig filterConfig;
private static Policy policy = null;
static{
if (policy == null) {
try {
policy = Policy.getInstance("/WEB-INF/classes/antisamy-slashdot.xml");
} catch (PolicyException e) {
log.warn("XssFilter - static policy instance error. PolicyException=" , e);
}
}
}
/* (non-Javadoc)
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
/* (non-Javadoc)
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
log.debug("doFilter() - start. ");
chain.doFilter(new XssRequestWapper((HttpServletRequest) request), response);
}
/* (non-Javadoc)
* @see javax.servlet.Filter#destroy()
*/
@Override
public void destroy() {
this.filterConfig = null;
}
class XssRequestWapper extends HttpServletRequestWrapper{
/**
* @param request
*/
public XssRequestWapper(HttpServletRequest request){
super(request);
}
/* (non-Javadoc)
* @see javax.servlet.ServletRequestWrapper#getParameterMap()
* 覆盖父类方法
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public Map<String,String[]> getParameterMap(){
log.debug("getParameterMap() - start. ");
Map<String,String[]> request_map = super.getParameterMap();
Iterator iterator = request_map.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry me = (Map.Entry)iterator.next();
String[] values = (String[])me.getValue();
for(int i = 0 ; i < values.length ; i++){
//System.out.println(values[i]);
values[i] = xssClean(values[i]);
}
}
return request_map;
}
/* (non-Javadoc)
* @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
* 覆盖父类方法
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public String getParameter(String name) {
log.debug("getParameter() - start. ");
String v=super.getParameter(name);
if(v==null)
return null;
if (needXssFilter) {
v = xssClean(v);
}
return v;
}
/* (non-Javadoc)
* @see javax.servlet.ServletRequestWrapper#getParameterValues(java.lang.String)
* 覆盖父类方法
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public String[] getParameterValues(String name) {
log.debug("getParameterValues() - start. ");
String[] v=super.getParameterValues(name);
if(v==null || v.length==0)
return v;
if (needXssFilter) {
for(int i=0;i<v.length;i++){
v[i]=xssClean(v[i]);
}
}
return v;
}
/**
* 过滤xss攻击违规输入, 2017, zhujq
* @param value
* @return
*/
private String xssClean(String value){
log.info("xssClean() - value=" + value + ", start. ");
AntiSamy antiSamy = new AntiSamy();
try {
//antiSamy无法将编码后的字符进行过滤,所以统一进行decode
value = URLDecoder.decode(value, "utf-8");
final CleanResults cr = antiSamy.scan(value, policy);
String cleanValue = cr.getCleanHTML();
//antiSamy会将双引号转换成""所以对过滤后的字符进行处理
cleanValue = StringEscapeUtils.unescapeHtml(cleanValue);
log.info("xssClean() - value=" + value + ", cleanValue=" + cleanValue);
return cleanValue;
} catch (PolicyException e) {
log.warn("xssClean() - value=" + value + ", PolicyException=", e);
} catch (ScanException e) {
log.warn("xssClean() - value=" + value + ", ScanException=", e);
} catch (Exception e) {
log.warn("xssClean() - value=" + value + ", Exception=", e);
}
return value;
}
}
}
过滤器web.xml配置
Filter对象将会在web应用启动时由服务器根据 web.xml 文件中的配置信息来创建,所以还需要在 web.xml 文件中配置注册自定义的 XssFilter,自定义filter必须放在默认过滤器之前。<!-- xss 过滤 -->
<filter>
<filter-name>XssFilter</filter-name>
<filter-class>com.cn21.edrive.product.api.rest.test.XssFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>XssFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>struts2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern> <!-- 把*.action 改为 /* -->
<dispatcher>REQUEST</dispatcher> <!-- 别忘了添加这两个参数 -->
<dispatcher>FORWARD</dispatcher>
</filter-mapping>