Javascript / jQuery DOM创建是否安全,直到它被添加到文档中?

时间:2011-04-08 11:15:21

标签: javascript jquery security dom

请仔细阅读本声明:让我们假设在将任何元素添加到{dom}中的document 所有不安全元素之前已删除。但他们最初创建。好的,让我们继续......


如果处理了一段用户文本,并且可以像这样加载:

var comment = 'I\'m a naughty person!!' +
              '<script src="http://blah.com/some_naughty_javascript.js">';
var $dom = $('<div>' + comment + '</div>');

本身以任何方式危险吗?我的观点是,创建DOM的简单行为可以以某种方式注入任何东西,还是只是简单地处理并创建结构?

例如:

var $dom = $('<script>alert("hi");</script>');

显然,在将消息添加到document之前,不会弹出消息hi。但是:

  • 以这种方式创建的任何标签或任何内容都是危险的吗?
  • javascript / jquery中的任何函数都可以“监视”以这种方式创建的元素,并在它被删除坏元素并放入文档之前对其进行操作吗?

Bounty Edit

因此,如下面的答案中所述,似乎这种方法不是很安全,特别是出于一个原因:

  • var $dom = $('<img src="blah.jpg"/>') - 无论对象是否已添加到文档中,都会立即请求图像。

这会产生处理HTML ajax请求的主要问题。例如,如果我们想从表单的输入中获取值:

$.ajax({
  url: 'test.php',
  success: function(responseHTML) {
    var inputs = $(responseHTML).find('form input');
  }
});

这会不由自主地导致浏览器请求所有图像。

Bounty奖励给任何人

  • 在没有上述问题的情况下,谁能提供一种处理ajax请求的好方法。
  • 理想情况下不提供正则表达式的答案...即如果我们想要$(responseHTML).find('img')做什么 - 用正则表达式删除图像标签不是一个选项,所以需要一种不显眼的方式来阻止它src来自加载,但仍然具有相同的属性,结构等。

4 个答案:

答案 0 :(得分:12)

  

这本身就是危险的吗?   办法?我的意思是,可以   以某种方式创建DOM的简单行为   注入任何东西,或只是简单   处理过,结构是   创建

简单地创建一个元素而不用将它附加到dom将不会导致任何脚本运行,因为此时它纯粹是一个对象(HtmlScriptElement)。当它实际附加到dom时,将由浏览器评估并运行脚本元素。据说我想,一个非常狡猾的人可能会利用你可能用来导致不良结果的某个框架或浏览器中出现的错误。

考虑这个例子:

<p>
    <input type="button" value="Store 'The Script' In Variable" id="store"/>
    <input type="button" value="Append 'The Script' To Dom" id="append"/>
</p>
<br/>
<p>
    <input type="button" value="Does nothing"/>
</p>
<h1>The Script</h1>
<pre id="script">
    $(function(){
        function clickIt(){
            $(this).clone().click(clickIt).appendTo("body");
        }
        $("input[type='button']").val("Now Does Something").click(clickIt);
    });
</pre>

var theScript;

$("#store").click(function() {
    theScript = document.createElement('script');
    var scriptText = document.createTextNode($("#script").text());
    theScript.appendChild(scriptText);
});

$("#append").click(function() {
    var head = document.getElementsByTagName('head')[0];
    head.appendChild(theScript);
});

单击store时,它将创建HtmlScriptElement并将其存储到变量中。您会注意到即使创建了对象也没有运行任何内容。单击append后,脚本将附加到dom并立即进行评估,按钮会执行不同的操作。

Code Example on jsfiddle


  

javascript / jquery中的任何函数都可以“监视”元素   以这种方式创造并采取行动   它之前被剥夺了坏处   元素和文件?

jQuery sort 为你做了这件事,因为它做了一些内部脚本eval


来自.append()

上的Karl Swedberg帖子
  

所有jQuery的插入方法都使用   内部的domManip函数   在和之前清理/处理元素   将它们插入DOM后。   domManip的其中一件事   函数确实是拉出任何脚本   即将插入和运行的元素   他们通过“evalScript例程”   而不是注入其余的   DOM片段。它插入了   脚本分开,评估它们,   然后从DOM中删除它们。   ...

您可以在调用<script/>时更改jQuery的行为以删除所有onclick, mouseover, etc并使用内联javascript append()清理其他元素但是这只会影响jQuery,因为有人可以轻松使用vanilla javascript附加<script/>元素。

Dom Mutation Events

Dom Level 2确实定义了一些Dom变异事件来捕获添加到dom中的元素,这些元素会向事件DOMNodeInserted发展。但是,在添加元素后会触发它。 注意 ,根据Raynos,这些目前是deprecated

  

DOMNodeInserted当节点有时触发   被添加为另一个节点的子节点。   此事件在发送之后发送   插入已经发生。目标   这个事件是节点   插入。气泡:是可取消:否   上下文信息:relatedNode持有   父节点

最后,似乎没有完全停止通过其他一些javascript将<script/>附加到dom。 (至少不是我能找到的)。

我建议的最佳方式是 从不信任用户输入,因为所有用户输入都是邪恶的 。当您进行dom操作时,请仔细检查以确保没有禁止标记,无论是<script/>还是普通<p/>元素,并在保留之前清理所有输入。

同样正如John指出的那样,您需要担心可以附加onclick事件或任何内联javascript事件处理程序的 任何 元素。

答案 1 :(得分:7)

对您的第一个例子的强制性回应

var comment = 'I\'m a naughty person!!' +
              '<script src="http://blah.com/some_naughty_javascript.js">';
var $dom = $('<div>' + comment + '</div>');

不要这样做。相反,您应该使用将文本视为文本的API,并且根本不会让您注入注入。在这个例子中,你应该这样做:

var $dom = $('<div>').text(comment);

...创建div然后设置其文本内容。永远不会将comment解析为HTML,因此浏览器不会对其执行任何危险操作。

继续讨论问题

如果我理解正确,您可以从任意HTML中解析信息,而不会让浏览器准备好显示它(例如,通过加载图像)。

这很棘手,因为内置于网络浏览器中的DOM是为了处理在某些时候会显示的内容而构建的。 jQuery(以及任何创建DOM节点的库)都受此限制。

DOM Level 2 defines an API to create documents与活动的document.implementation.createHTMLDocument(title)完全分开。在我的测试中,当在其中一个文档上创建img时,不会加载任何内容:

var doc = document.implementation.createHTMLDocument(''),
    img = doc.createElement('img');
img.src = 'http://example.com/image.jpg'; // Nothing happens.
// Alternatively…
doc.body.innerHTML = '<img src="http://example.com/image.jpg">'; // Nope.

因此,以这种方式创建的文档似乎是一个很好的沙箱来解析和探索HTML。您甚至可以在不同文档($(doc.body))中的节点周围创建jQuery包装器,并通过jQuery API进行探索。当您找到要查找的节点时,可以将它们转换回HTML以插入活动文档,或使用methods like importNode() and adoptNode()将它们直接传输到活动文档中。

不幸的是,对所有这些的支持是新的。 Firefox支持版本4及更高版本中的createHTMLDocument(类似createDocument方法,处理XML,available in older versions),Internet Explorer支持in version 9 and up。此外,据我所知,规范不保证不会在这些文档上预加载图像和脚本。

更好的解决方案是避免浏览器的HTML解析器。一些JavaScript HTML 解析器最近出现了。最简单的可能是John Resig的Pure JavaScript HTML Parser。您可以为其提供HTML,并在遇到新标记,属性和文本时触发回调。从这些回调中,您可以创建新的HTML,构建DOM节点或以您喜欢的任何形式存储文档 - 您可以忽略您认为危险的属性和节点。

你可以找到这个in Dan Kaminsky’s Interpolique的例子,这是一个概念验证,旨在一劳永逸地杀死XSS和SQL注入。该项目尚未开始,但如果您下载Interpolique,您会发现htmlparser.js底部有一个safeParse()函数,该函数使用标记名称和属性的白名单,并抛弃其他所有内容。

jsdom是一个完整的(直到DOM级别2,有一些级别3)用JavaScript编写的HTML DOM - 您可以使用它来安全地使用HTML。你甚至可以加载自己的jQuery副本。但是,它是为CommonJS编写的,没有考虑到浏览器兼容性。我不知道它是否适用于大多数没有修改的Web浏览器。它也是一个很大的图书馆。

如果可能的话,理想的解决方案是以HTML以外的格式提供AJAX响应。您是否需要包含额外的,不安全的HTML?如果你在服务器上做的工作只返回你需要的东西......

{
    "inputs": [
        '<input …>',
        '<input …>'
    ],
}

......你在客户端的工作变得容易多了。

答案 2 :(得分:2)

很好的问题。看起来可以在其中注入脚本和放置事件处理程序。我使用以下HTML进行了测试:

<!DOCTYPE html>
<html lang="en">
    <head>  
        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
        <script type="text/javascript"> 
            <!-- 
            $(function() {
                var $dom = $('<script>$(".testbutton").live("click", function() { alert("hi") });</script>');

                $(".firstbutton").click(function() {
                    $("BODY").append($dom);
                });
            });
            -->
        </script>
    </head>

    <body style="padding:0">            
        <button class="firstbutton">Click this first</button>

        <button class="testbutton">Then this</button>
    </body>
</html>

在单击第一个按钮并将脚本标记添加到DOM之前,您可以看到第二个按钮无效。

如果采用用户表单输入并动态插入页面,则可能会滥用此功能。在这种情况下,应该明确地使用数据清洁。

这是我从未想过的 - 感谢提高它。

答案 3 :(得分:0)

似乎只要没有附加到DOM,脚本就不会起作用。

$(function ()
{
    var ss = document.createElement('script');
    var scr = 'alert("bah");';
    var tt = document.createTextNode(scr);
    ss.appendChild(tt);
    var hh = document.getElementsByTagName('head')[0];
    //hh.appendChild(ss);
});

$(function ()
{
    var ss = document.createElement('script');
    var scr = 'alert("bah");';
    var tt = document.createTextNode(scr);
    ss.appendChild(tt);
    var hh = document.getElementsByTagName('head')[0];
    hh.appendChild(ss);
});