上篇写了怎么使用antisamy防御xss脚本攻击,下篇简单分析一下antisamy过滤的原理。
基础步骤的分析
从上篇的最基础的实现逻辑分析具体的实现,下面这段最基础实现非常好理解,看注释就能知道实现步骤。
//定义过滤的策略
Policy policy = Policy.getInstance("file"); //此处的file指使用的策略文件,如antisamy-ebay.xml
//对html输入进行过滤
Antisamy antisamy = new Antisamy();
CleanResult cr = antisamy.scan(innerHtml,policy);
//输出过滤后安全的html或异常信息
String cleanHtml = cr.getCleanHTML();
String errMsg = cr.getErrorMessages();
项目源码
重点是解析过程。在网上查阅了一些资料,配合阅读了项目的源码。确认策略是将html解析成dom树,然后扫描,源码中的代码如下
扫描流程
public CleanResults scan(String html)
throws ScanException
{
if (html == null) {
throw new ScanException(new NullPointerException("Null input"));
}
this.errorMessages.clear();
int maxInputSize = this.policy.getMaxInputSize();
if (maxInputSize < html.length()) {
addError("error.size.toolarge", new Object[] { Integer.valueOf(html.length()), Integer.valueOf(maxInputSize) });
throw new ScanException((String)this.errorMessages.get(0));
}
this.isNofollowAnchors = this.policy.isNofollowAnchors();
this.isValidateParamAsEmbed = this.policy.isValidateParamAsEmbed();
long startOfScan = System.currentTimeMillis();
try
{
CachedItem cachedItem = (CachedItem)cachedItems.poll();
if (cachedItem == null) {
cachedItem = new CachedItem();
}
html = stripNonValidXMLCharacters(html, cachedItem.invalidXmlCharMatcher);
DOMFragmentParser parser = cachedItem.getDomFragmentParser();
try
{
parser.parse(new InputSource(new StringReader(html)), this.dom);
} catch (Exception e) {
throw new ScanException(e);
}
processChildren(this.dom, 0);
String trimmedHtml = html;
StringWriter out = new StringWriter();
OutputFormat format = getOutputFormat();
HTMLSerializer serializer = getHTMLSerializer(out, format);
serializer.serialize(this.dom);
String trimmed = trim(trimmedHtml, out.getBuffer().toString());
Callable cleanHtml = new Callable(trimmed) {
public String call() throws Exception {
return this.val$trimmed;
}
};
this.results = new CleanResults(startOfScan, cleanHtml, this.dom, this.errorMessages);
cachedItems.add(cachedItem);
return this.results;
}
catch (SAXException e)
{
throw new ScanException(e);
}
catch (IOException e) {
throw new ScanException(e);
}
}
这段代码逻辑上比较清楚,简单的梳理一下:
- 确定输入的html不为空,为空的话抛出错误
- 确定输入的html长度不超过最大值,超出的话抛出错误
- 将html中的低价打印字符过滤掉(防止解析失败)
- 将html解析成dom树
- 处理dom树返回干净的html
html解析成dom的流程
代码中间解析成dom树的代码,查阅了相关资料,思路是这样的:
4 遍历dom树进行以下处理:
4.1 判断深度是否达到250,是返回异常
4.2 判断节点是否为comment,特殊处理后返回。
4.2.1 判断是否显示comment,否则删除comment节点
4.2.2 如果需要显示节点,则过滤节点的数据
4.3 判断节点是否为空元素,根据设置判断是否需要删除节点。4.4 判断节点是否为CDATA,过滤节点数据
4.4.1 输出异常,同时创建text节点代替CDATA节点。
4.5 判断是否为ProcessingInstruction,是删除该节点4.6 获取当前节点的tagRule,根据tagRule规则进行不同处理
4.6.1 判断是否使用默认的tagRule
4.6.2 要么encode节点(节点内容进行编码)
4.6.3 要么filter节点(删除节点,保留节点子节点)
4.6.4 要么validate节点
对style节点进行特殊处理 对其他节点的属性一个一个验证,如果发现校验失败,根据配置,进行以下处理: 4.6.4.1 removeTag :删除当前元素 4.6.4.2 filterTag : 过滤当前元素 4.6.4.3 encodeTag : 编码元素 4.6.4.4 remoteAttr: 默认配置,删除属性
4.6.5 要么truncate节点(删除节点属性,以及删除子节点中非text类型的节点)
4.6.6 要么删除节点(包括子节点都进行删除)
如果仅仅为了使用项目过滤文本,可以不用过分关注解析细节,了解其流程即可。