AntiSamy是OWASP的一个开源项目,通过对用户输入的 HTML / CSS / JavaScript 等内容进行检验和清理,确保输入符合应用规范。AntiSamy被广泛应用于Web服务对存储型和反射型XSS的防御中。
AntiSamy直接导入到工程即可,但是其运行依赖xercesImpl、batik、nekohtml,这些依赖默认会一起导入
<dependency>
<groupId>org.owasp.antisamygroupId>
<artifactId>antisamyartifactId>
<version>1.5.5version>
dependency>
AntiSamy对“恶意代码”的过滤依赖于策略文件。策略文件规定了AntiSamy对各个标签、属性的处理方法,策略文件定义的严格与否,决定了AntiSamy对XSS漏洞的防御效果。
在AntiSamy的jar包中,包含了几个常用的策略文件
我们可以自定义策略文件来过滤用户输入,但更多的会是基于现有的策略文件进行稍微的调整,以使其更贴合项目的实际需求。
要描述某种特定规则,XML无疑是个不错的选择,而AntiSamy策略文件也正是采用了XML格式。如图所示,除去文件头的AntiSamy策略文件可以分为八个部分
1)、directives
全局配置,对AntiSamy的过滤验证规则、输入及输出的格式进行全局性的控制
<directives>
<directive name="omitXmlDeclaration" value="true"/>
<directive name="omitDoctypeDeclaration" value="true"/>
<directive name="maxInputSize" value="200000"/>
<directive name="useXHTML" value="true"/>
<directive name="formatOutput" value="true"/>
<directive name="nofollowAnchors" value="true" />
<directive name="validateParamAsEmbed" value="true" />
<directive name="embedStyleSheets" value="false"/>
<directive name="connectionTimeout" value="5000"/>
<directive name="maxStyleSheetImports" value="3"/>
directives>
2)、common-regexps
公用正则表达式,需要使用正则的时候可以通过name直接引用
<common-regexps>
<regexp name="numberOrPercent" value="(\d)+(%{0,1})"/>
<regexp name="paragraph" value="([\p{L}\p{N},'\.\s\-_\(\)\?]|&[0-9]{2};)*"/>
<regexp name="htmlId" value="[a-zA-Z0-9\:\-_\.]+"/>
common-regexps>
假设后文需要使用”htmlId”这一正则时,直接根据对应的name属性进行引用即可
<attribute name="id" description="The 'id' of any HTML attribute should not contain anything besides letters and numbers">
<regexp-list>
<regexp name="htmlId"/>
regexp-list>
attribute>
3)、common-attributes
通用的属性需要满足的输入规则,其中包括了标签和css的属性;在tag和css的处理规则中会引用到这些属性
<common-attributes>
<attribute name="classid">
<regexp-list>
<regexp name="anything" />
regexp-list>
attribute>
<attribute name="autocomplete">
<literal-list>
<literal value="on"/>
<literal value="off"/>
literal-list>
attribute>
common-attributes>
4)、global-tag-attributes
所有标签的默认属性需要遵守的规则
<global-tag-attributes>
<attribute name="id"/>
<attribute name="style"/>
<attribute name="title"/>
<attribute name="class"/>
<attribute name="lang"/>
global-tag-attributes>
5)、tags-to-encode
需要进行编码处理的标签
<tags-to-encode>
<tag>gtag>
<tag>grintag>
tags-to-encode>
6)、tag-rules
tag的处理规则,共有三种处理方式
remove
对应的标签直接删除,如script标签处理规则为删除
<tag name="script" action="remove"/>
truncate
对应的标签进行缩短处理,直接删除所有属性,只保留标签和值
如标题只保留标签和值
<tag name="title" action="truncate"/>
validate
对应的标签的属性进行验证,如果tag中定义了属性的验证规则,按照tag中的规则执行;如果标签中未定义属性,则按照
中定义的处理
<tag name="head" action="validate"/>
7)、css-rules
CSS的处理规则
<css-rules>
<property name="bottom" default="auto" description="">
<category-list>
<category value="visual"/>
category-list>
<literal-list>
<literal value="auto"/>
<literal value="inherit"/>
literal-list>
<regexp-list>
<regexp name="length"/>
<regexp name="percentage"/>
regexp-list>
property>
<property name="color" description="">
<category-list>
<category value="visual"/>
category-list>
<literal-list>
<literal value="inherit"/>
literal-list>
<regexp-list>
<regexp name="colorName"/>
<regexp name="colorCode"/>
<regexp name="rgbCode"/>
<regexp name="systemColor"/>
regexp-list>
property>
css-rules>
8)、allowed-empty-tags
允许没有内容的标签
<allowed-empty-tags>
<literal-list>
<literal value="br"/>
<literal value="hr"/>
<literal value="a"/>
<literal value="img"/>
<literal value="link"/>
<literal value="iframe"/>
<literal value="script"/>
<literal value="object"/>
<literal value="applet"/>
<literal value="frame"/>
<literal value="base"/>
<literal value="param"/>
<literal value="meta"/>
<literal value="input"/>
<literal value="textarea"/>
<literal value="embed"/>
<literal value="basefont"/>
<literal value="col"/>
<literal value="div"/>
literal-list>
allowed-empty-tags>
大概清楚了每个标签代表的意义,便能够很容易地写出符合自己需求的策略文件了。我们再来简单的看下jar包中几个常见策略文件
antisamy-anythinggoes.xml
允许所有有效的HTML和CSS元素输入(但能拒绝JavaScript或跟CSS相关的网络钓鱼攻击),因为它包含了对于每个元素的基本规则,所以你在裁剪其它策略文件的时候可以把它作为一个知识库,一般不建议使用。
antisamy-ebay.xml
eBay 是当下最流行的在线拍卖网站之一。它是一个面向公众的站点,因此它允许任何人发布一系列富HTML的内容,允许输入的内容列表包含了比 Slashdot 更多的富文本内容,所以它的受攻击面也要大得多。
该策略相对安全,适用于电子商务网站。
antisamy-myspace.xml
MySpace 是最流行的一个社交网站之一。用户允许提交除了JavaScript之外的几乎所有他们想用的HTML和CSS。
MySpace现在用一个黑名单来验证用户输入的HTML,相对较危险,不建议使用。
antisamy-slashdot.xml
Slashdot 是一个提供技术新闻的网站,其安全策略非常严格。用户只能提交下列的HTML标签:、、、、
,并且还不支持CSS。
该策略文件来实现了类似的功能,允许所有文本格式的标签来直接修饰字体、颜色或者强调作用,适用于新闻网站的评论过滤。
antisamy-tinymce.xml
只允许文本格式通过,相对较安全
antisamy.xml
默认规则,允许大部分HTML通过
其实 AntiSamy 在使用上是十分简单的,指定策略文件后构建AntiSamy对象,然后将数据传入AntiSamy对象进行过滤即可
// 需要过滤的数据
String taintedHTML = "HELLO WORD!";
// 根据策略文件创建过滤策略
Policy policy = Policy.getInstance( "antisamy-ebay.xml");
// 根据策略对数据进行过滤
AntiSamy antiSamy = new AntiSamy();
CleanResults cr = antiSamy.scan( taintedHTML, policy);
taintedHTML = cr.getCleanHTML();
对于一个项目来说,基本每个输入都需要进行检查,所以一般我们会结合Filter进行使用。
Filter是一个典型的过滤链,可以用来对 HttpServletRequest 进行预处理,或者是对 HttpServlerResponse 进行后处理。
定义自定义过滤器 XssFilter 类,实现 Filter 接口,并对 doFilter( ServletRequest request, ServletResponse response, FilterChain chain) 方法进行重写。为了对用户请求进行处理,需要重写 ServletRequest ,并交由 FilterChain 执行
/**
* XSS (Cross Site Scripting) 过滤器
* @author zhangcs
*/
public class XssFilter implements Filter{
@SuppressWarnings("unused")
private FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 传入重写后的Request
chain.doFilter( new XssRequestWrapper( ( HttpServletRequest)request), response);
}
@Override
public void destroy() {
this.filterConfig = null;
}
}
新建一个自定义 HttpServletRequest 包装类 XssRequestWrapper ,继承 HttpServletRequestWrapper 类,并对getParameter( String param)、getParameterValues( String param)以及 getHeader( String param) 等方法进行重写。
例如我们使用的MVC框架为SpringMVC,需要对用户输入的参数值进行过滤,则重写 getParameterValues( String name) 方法
/**
* 自定义的XSS请求包装器
* @author zhangcs
*/
public class XssRequestWrapper extends HttpServletRequestWrapper {
public XssRequestWrapper( HttpServletRequest request) {
super(request);
}
@Override
public String[] getParameterValues( String name){
String[] values = super.getParameterValues( name);
if ( values == null){
return null;
}
int len = values.length;
String[] newArray = new String[len];
for (int j = 0; j < len; j++){
// 过滤
newArray[j] = xssClean( values[j]);
}
return newArray;
}
/**
* 策略文件
* 注意,需要将要使用的策略文件放到项目资源文件路径下
* */
private static String antiSamyPath = XssRequestWrapper.class.getClassLoader()
.getResource( "antisamy-ebay.xml").getFile();
/**
* AntiSamy过滤数据
* @param taintedHTML 需要进行过滤的数据
* @return 返回过滤后的数据
* */
private String xssClean( String taintedHTML){
try{
// 指定策略文件
Policy policy = Policy.getInstance( antiSamyPath);
// 使用AntiSamy进行过滤
AntiSamy antiSamy = new AntiSamy();
CleanResults cr = antiSamy.scan( taintedHTML, policy);
taintedHTML = cr.getCleanHTML();
}catch( ScanException e) {
e.printStackTrace();
}catch( PolicyException e) {
e.printStackTrace();
}
return taintedHTML;
}
}
Filter对象将会在web应用启动时由服务器根据 web.xml 文件中的配置信息来创建,所以还需要在 web.xml 文件中配置注册自定义的 XssFilter
<filter>
<filter-name>XSSFilterfilter-name >
<filter-class>cn.ghr.ehr.filter.XssFilterfilter-class >
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
filter>
<filter-mapping>
<filter-name>XSSFilterfilter-name >
<url-pattern>/url-pattern>
filter-mapping>