Aiur

zellux 的博客

跨站脚本攻击和 BluePrint

Permalink

Blueprint: Robust prevention of cross-site scripting attacks for existing browsers

这篇论文提出了一种防范是跨站脚本攻击(XSS)的新的方法,发在IEEE S&P 2009上,作者是UIUC的Mike Ter Louw。

所谓跨站脚本攻击,简单地说就是在网页中注入非法的脚本代码,从而达到攻击的效果。比较著名的例子有当年在MySpace上泛滥的Samy蠕虫,通过特殊的脚本注入手段,每一位访问Samy主页的用户,他们的主页都会被修改加上一段Samy is my hero文字,并且他们的主页也会被植入攻击代码,从而把这段脚本扩散给更多的用户。

通常防范跨站脚本攻击的方式有两种。一种做在服务器端,为每一段用户上传的内容做检查,并剔除恶意代码。但这种方式很难保证能过滤掉所有的恶意字符串,一方面攻击方法防不甚防,有兴趣的朋友可以参考下XSS Cheat Sheet,上面给出了很多一般人很难想到的攻击代码的组合方式。另一方面由于现在大多数论坛和博客都支持一些基本的文本修饰标签,所以简单的标签剔除或者重新编码都不可行。

另一种方法是做在浏览器端,但是由于浏览器无法区分某一段脚本到底是来源于不可信的用户还是可信的站点,所以这种方法实现起来也有很大的困难。

这里实现防范措施的一个难点在于,Web应用把生成HTML的返回给浏览器后,就不参与浏览器的HTML解析工作了。这样浏览器就不知道哪部分出现脚本是安全的,哪部分出现是不安全的。

BluePrint就着眼于这个点,提出了一种让Web应用“参与”HTML解析工作的设计。下面通过论文里面的一个例子,简单介绍下它的防范机制。

假如一位恶意的用户在一个博客上上传了这样一段含有恶意代码的留言:

1
2
3
4
5
6
7
8
9
10
<p>
Here is a page you might find
<b """><script>doEvil(. . .)</script>">very</b>
interesting:
<a href=" &#14; javasc&#x0A;ript:doEvil(. . .);">
Link</a>
</p><p style="nop:expres/*xss*/sion(doEvil(. . .))">
Respectfully,
Eve
</p>

可以看到,这段代码里包含了很多可能引发脚本执行的代码,而要在服务器端把这些所有隐藏的攻击可能找出来是一件比较困难的事。那么BluePrint是怎么在不知道这段代码是否含有恶意代码的前提下处理的呢?

首先,这种由用户上传的不可信的字符串会先在服务器端被解析成一棵树,就像HTML在浏览器中被解析一样,这棵HTML解析树可以用一些简单的DOM API来生成,例如appendChild, createElement等。这些描述如何生成HTML解析树的方法会和数据值(URL、标签属性等)一起,通过特殊的编码(Base64)传递给浏览器。例如上面这段代码,最后在浏览器接收到的HTML中,会变成这样:

1
2
3
4
5
6
7
<code style="display:none;" id="__bp1">
=Enk/sCkhlcmUgaXMgYSBwYWdlIHlvdSBta...
=SkKICAgICI+dmVyeQ===C/k/QIGhlbHBmd...
=ECg===C/Enk/gCiAgUmVzcGVjdGZ1bGx5L...
</code><script id="__bp1s">
__bp__.cxPCData("__bp1", "__bp1s");
</script>

在浏览器端,这段特殊的代码会被JS库解析成自定义的命令和数据格式,并由前面提到的DOM API动态生成这些HTML结点,从而达到和传统的方式一样的显示效果。当然可信的HTML代码,例如文章正文,还是按传统的方式传输的。

通过这种方式,BluePrint绕过了浏览器对不可信代码的解析,从而防止了不可信代码里内嵌的脚本的执行

此外还有一些细节的问题,例如为什么使用Base64编码来描述自定义的命令和数据,而不是常用的例如UTF-8呢?这是因为使用UTF-8的话攻击者就有可能通过构造一段特殊的字符串,而这段字符串对应的编码恰好能起到攻击作用。而使用Base64编码就不会有这个问题。

攻击例子中的第5行和第7行还分别包括了通过恶意URL和CSS风格实现的代码,前面提到的措施还不足以防范这两种类型的攻击。论文里面也提到了相应的解决方案,这里不再赘述,有兴趣的朋友可以搜索论文阅读相关部分。

把BluePrint整合到现有的应用程序里也不难,只要把包含不可信内容显示部分的代码重新加一层包装就行了,像这样:

1
2
3
4
5
6
7
8
9
10
// Code for trusted blog content
// appears untransformed aboveˆˆ.
<?php foreach ($comments as $comment): ?>
    <li>
        <?php
$model = Blueprint::cxPCData($comment);
echo($model);
        ?>
    </li>
<?php endforeach; ?>

在BluePrint的开销方面,包含25个用户评论的wordpress页面产生速度慢了55%,不过作者提到wordpress本身还有HTML解析和恶意代码检查过滤的功能,用了BluePrint后就不需要这些冗余的检查了,所以把这部分代码去掉会快不少。另外由于不可信内容都需要动态的被解码并创建相应的HTML结点,浏览器端的显示速度慢了很多,作者也解释到这种解析开销其实并不重要,因为通常看一篇博文的时候都是先看内容,由于文章内容本身是可信的,所以会以传统的方式传输并显示,若干秒后再显示评论也未必会对用户体验造成太大的影响。

这篇论文给我的感觉是思路很清晰,抓住了主要的难点后用了对应的方法绕过了浏览器的HTML解析。不过应用面上还有一些局限,只能防止不可信代码中脚本的执行,对于需要执行脚本的情形(例如Blogger上的Gadget)就不适用了。MIT去年发在EuroSys ‘09上的BFlow就是针对这样一种情形,通过类似于Flume的标签系统,使得不可信的脚本读取了隐私数据后就无法将它们传输给不可信的网站。

用beamer做了slides,在这里可以下载到: http://zellux-notes.googlecode.com/hg/slides/blueprint/

评论