实际应用中,HTML文档的最大深度是多少?

25
我想允许嵌入HTML,但避免由于深度嵌套的HTML文档导致某些浏览器崩溃的DoS攻击。我希望能够容纳99.9%的文档,但拒绝那些嵌套过深的文档。
两个相关问题:
1. 浏览器内置了哪些文档深度限制?例如,浏览器X无法解析或不会构建深度>某个限制的文档。 2. 网上是否有可用的文档深度统计数据?是否有一个网站提供Web统计数据,说明Web上一些真实文档的文档深度低于某个值。
文档深度定义为从文档中任何节点到文档根需要的最大父遍历数加1。例如,在...
<html>                   <!-- 1 -->
  <body>                 <!-- 2 -->
    <div>                <!-- 3 -->
      <table>            <!-- 4 -->
        <tbody>          <!-- 5 -->
          <tr>           <!-- 6 -->
            <td>         <!-- 7 -->
              Foo        <!-- 8 -->

最大深度为8,因为文本节点“Foo”有8个祖先节点。这里的祖先节点是非严格解释的,即每个节点都是自己的祖先和后代。 Opera提供了一些表嵌套统计数据,表明99.99%的文档的表嵌套深度小于22,但该数据不包含整个文档深度。
编辑:
如果人们想批评HTML清理库而不是回答这个问题,请这样做。 http://code.google.com/p/owasp-java-html-sanitizer/wiki/AttackReviewGroundRules解释了如何找到代码、在哪里找到一个让你尝试攻击的测试平台以及如何报告问题。
编辑:
我问了Adam Barth,他非常友好地指出了处理这个问题的webkit代码。
Webkit至少会强制执行这个限制。当创建一个treebuilder时,它会接收一个可配置的树限制:
m_treeBuilder(HTMLTreeBuilder::create(this, document, reportErrors, usePreHTML5ParserQuirks(document), maximumDOMTreeDepth**(document)))
并且它通过了block-nesting-cap测试。

2
我很好奇,你是从哪里得到的这个想法,认为存在嵌套限制或者“深度嵌套的HTML文档会导致某些浏览器崩溃”?我从未听说过这种情况。 - Wesley Murch
3
我认为嵌套HTML并不是您最紧迫的问题。用户可以使用HTML做很多恶意的事情。http://www.codinghorror.com/blog/2008/10/programming-is-hard-lets-go-shopping.html - Nick ODell
1
@NickODell,我知道用户可以使用HTML做很多恶意的事情。现在这是最紧迫的问题,因为它是第一轮攻击审查中唯一仍未解决的问题。 - Mike Samuel
1
@NickODell,感谢提供链接。这个实现不容易受到那篇文章中概述的问题的影响——它不使用正则表达式或任何其他基于模式的过滤器。它对HTML进行标记化处理,应用标签和元素白名单,然后使用规范化渲染器生成一个语法上有效的结果。 - Mike Samuel
1
我认为实际上没有限制。如果有内置限制,浏览器就不会崩溃。即便如此,您仍然可以轻松测试它。只需创建一个具有各种深度的嵌套元素的文档。您可以编写一个PHP脚本来完成此操作。如果从10开始,您可以在每次尝试时将深度加倍。如果浏览器崩溃,则知道限制在LastOkDepth和LastOkDepth * 2之间。然后,您可以从中间开始,并每次将范围减半。这样,您可以在几十次尝试中找到每个浏览器的限制。 - GolezTrol
显示剩余16条评论
2个回答

21

或许询问coderesearch@google.com会有所帮助。他们2005年的研究(http://code.google.com/webstats/)没有涵盖你提出的问题,但是他们采样了超过10亿个文档,并且对于任何你认为值得研究的内容都很感兴趣。

--[更新]--

这是我编写的一个简单脚本,用来测试我拥有的浏览器(将要嵌套的元素数量放入查询字符串中):

var n = Number(window.location.search.substring(1));

var outboundHtml = '';
var inboundHtml = '';

for(var i = 0; i < n; i++)
{
    outboundHtml += '<div>' + (i + 1);
    inboundHtml += '</div>';
}

var testWindow = window.open();
testWindow.document.open();
testWindow.document.write(outboundHtml + inboundHtml);
testWindow.document.close();

以下是我的调查结果(可能与我的机器有关,Win XP,3GB RAM):

  • Chrome 9:可以呈现3218个嵌套元素,3129个会导致选项卡崩溃。(我知道Chrome 9已经很旧了,但更新程序在我们公司的局域网中无法使用。)
  • Safari 5:可以呈现3477个元素,3478个会导致浏览器完全关闭。
  • IE8:可以呈现1000000+个元素(如果内存允许),但当滚动/移动鼠标等操作进入高4位数时,性能会显著下降,因为事件冒泡。超过10000个元素似乎会锁定,但我认为只是需要很长时间,因此会产生DoS效果。
  • Opera 11:根据我所知,仅受内存限制,即我的脚本对于10000000个元素而言会耗尽内存。对于确实呈现的大型文档,似乎没有像IE那样的性能下降。
  • Firefox 3.6:可以呈现大约1500000个元素,但测试超出该范围会导致浏览器崩溃并显示Mozilla Crash Reporter,或者只是挂起。有时,先前有效的数字会导致后续失败,但较大的数字(大约1700000)将直接使Firefox崩溃并重启。

更多关于Chrome的信息:

将DIV更改为SPAN导致Chrome能够在崩溃之前嵌套9202个元素。因此,HTML的大小并不是原因(尽管SPAN元素可能更轻量级)。

嵌套2077个单元格(<table><tr><td>)可以工作(6231个元素),直到您滚动到第445个单元格,然后它就会崩溃,因此不能嵌套445个单元格(1335个元素)。

使用从脚本生成的文件进行测试(而不是写入新窗口)会提高一点容忍度,但Chrome仍会崩溃。

您可以在崩溃之前嵌套1409个列表项(<ul><li>),这很有趣,因为:

  • Firefox在99个列表项之后停止缩进,可能是程序约束。
  • Opera在250、376、502、628、754、880...处保持缩进,但存在问题。

在IE8中设置DOCTYPE有效(即将其置于标准模式中,例如var outboundHtml = '<!DOCTYPE html>'):它不能嵌套792个列表项(选项卡崩溃/关闭)或1593个DIV。无论测试是从脚本生成还是从文件加载,它在IE8中都没有任何区别。

因此,浏览器的嵌套限制显然取决于攻击者注入的HTML元素类型和布局引擎。可能会有一些HTML比这小得多。如果您要允许用户发布在页面上呈现的HTML,则值得考虑对嵌套元素进行限制,如果存在慷慨的大小限制。


谢谢。我没有得到统计数据,但我得到了指向强制执行此操作的 WebKit 代码的指针。我已经在 OP 中编辑了这些指针。 - Mike Samuel
关于 Firefox,我自己也遇到了这个可爱的小 bug:https://bugzilla.mozilla.org/show_bug.cgi?id=256180。结果是,深度超过 200 的任何元素都不会被渲染。您可以通过一个简单的脚本来测试它,该脚本创建一个超过 200 深度的字符串(我为了论证使用了 500),其中包含一个已知的字符串,然后测试当您呈现它时是否出现已知的字符串。 - Gert Sønderby

4

太棒了!但是浏览器会崩溃吗? - Lee Kowalkowski
@LeeKowalkowski,WebCore不应该这样做。它将节点的子级折叠到父级中,而不是增加堆栈,就像http://trac.webkit.org/browser/trunk/Source/WebCore/html/parser/HTMLConstructionSite.cpp#L100一样,但其他浏览器会崩溃。 - Mike Samuel
我成功地让Chrome、Safari和IE8崩溃了,而Firefox和Opera似乎只是因为内存不足而停止运行(无法确定是我的脚本还是文档的问题)。我已经在我的答案中包含了我的发现。 - Lee Kowalkowski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接