我可以要求浏览器不在元素中运行 <script> 吗?

Posted

技术标签:

【中文标题】我可以要求浏览器不在元素中运行 <script> 吗?【英文标题】:Can I ask a browser to not run <script>s within an element? 【发布时间】:2014-11-13 18:40:32 【问题描述】:

是否可以告诉浏览器不要从 html 文档的特定部分运行 javascript

喜欢:

<div script="false"> ...

它可以用作附加的安全功能。我想要的所有脚本都加载到文档的特定部分。文档的其他部分不应有脚本,如果有,则不应运行。

【问题讨论】:

我不知道。这是评论而不是答案,因为我不能说 100% @chiastic-security 只能在 DOM 加载完成后才能遍历 DOM。到那时,该块中的任何 JS 都会执行。 我能想到的最接近的是内容安全策略,您可以在其中按来源限制脚本(也许这就是您想要的)。例如。通过指定script-src:"self",您只允许来自您的域的脚本在页面中运行。如果您有兴趣,请阅读this article from Mike West about CSP。 @chiastic-security 在标头已发送后,您打算如何放宽标头中指定的限制?无论如何,由于 CSP 默认禁用内联脚本,并且除非列入白名单,否则不会加载远程脚本,为什么需要将它与其他任何东西结合使用? CSP 可能是 OP 的最佳选择;我希望有人留下 CSP 答案。 如果您担心有人将任意块注入您的 HTML,您提出的解决方案将如何防止他们注入 &lt;/div&gt; 以关闭此 DOM 元素,然后启动一个新的 &lt;div&gt;将是未运行脚本的兄弟姐妹? 【参考方案1】:

是的,你可以 :-) 答案是:Content Security Policy (CSP)。

Most modern browsers support this flag,它告诉浏览器只从受信任的外部文件加载 JavaScript 代码并禁止所有内部 JavaScript 代码!唯一的缺点是,您不能在整个页面中使用任何内联 JavaScript(不仅仅是单个 &lt;div&gt;)。虽然可以通过动态包含具有不同安全策略的外部文件中的 div 来解决此问题,但我不确定。

但是,如果您可以更改您的网站以从外部 JavaScript 文件加载所有 JavaScript,那么您可以使用此标头完全禁用内联 JavaScript!

这是一个很好的示例教程:HTML5Rocks Tutorial

如果您可以将服务器配置为发送此 HTTP-Header 标志,世界将会变得更美好!

【讨论】:

+1 这真的很酷,我不知道它存在! (请注意,您的 wiki 链接直接指向德语版本)以下是浏览器支持的概要:caniuse.com/#feat=contentsecuritypolicy 请注意,即使您这样做,在页面上允许未转义的用户输入仍然是一个坏主意。这基本上将允许用户更改页面以使其看起来像他们想要的那样。即使将所有内容安全策略 (CSP) 设置设置为最大值(禁止内联脚本、样式等),用户仍然可以使用 GET 请求的图像 src 执行跨站点请求伪造 (CSRF) 攻击,或诱使用户进入单击 POST 请求的表单提交按钮。 @Ajedi32 当然,您应该始终清理用户输入。但是 CSP 甚至可以为图像或 css 之类的 GET 请求设置策略,它不仅会阻止它们,甚至会通知您的服务器! @Falco 我读过the docs,我的理解是您只能将这些请求限制到给定域,而不是域上的一组特定子页面。假设您设置的域与您的站点相同,这意味着您仍然会受到 CSRF 攻击。 @Falco 是的,这基本上就是 StackExchange 使用新的 Stack Snippets 功能所做的事情:blog.***.com/2014/09/… 如果您正确清理输入,则不需要单独的域。【参考方案2】:

您可以使用beforescriptexecute 事件阻止&lt;script&gt; 加载的JavaScript:

<script>
  // Run this as early as possible, it isn't retroactive
  window.addEventListener('beforescriptexecute', function(e) 
    var el = e.target;
    while(el = el.parentElement)
      if(el.hasAttribute('data-no-js'))
        return e.preventDefault(); // Block script
  , true);
</script>

<script>console.log('Allowed. Console is expected to show this');</script>
<div data-no-js>
  <script>console.log('Blocked. Console is expected to NOT show this');</script>
</div>

请注意,beforescriptexecute 是在 HTML 5.0 中定义的,但在 HTML 5.1 中已被删除。 Firefox 是唯一实现它的主流浏览器。

如果您在页面中插入一堆不受信任的 HTML,请注意在该元素内阻止脚本不会提供更多安全性,因为不受信任的 HTML 可以关闭沙盒元素,因此脚本将被放置在外部并且运行。

这不会阻止&lt;img onerror="javascript:alert('foo')" src="//" /&gt;之类的内容。

【讨论】:

小提琴似乎没有按预期工作。我应该看不到“被阻止”的部分吧? @SalmanA 没错。可能您的浏览器不支持beforescriptexecute 事件。它适用于 Firefox。 可能是因为它在 Chrome 中无法使用,但提供了 sn-p,尽管我看到您只是将其转换为 sn-p :-) beforescriptexecute 似乎不受支持,并且大多数主流浏览器都不会支持。 developer.mozilla.org/en-US/docs/Web/Events/beforescriptexecute【参考方案3】:

有趣的问题,我认为这是不可能的。但即使是这样,听起来也像是一个黑客攻击。

如果该 div 的内容不受信任,那么您需要先在服务器端对数据进行转义,然后才能在 HTTP 响应中发送数据并在浏览器中呈现。

如果您只想删除 &lt;script&gt; 标记并允许其他 html 标记,则只需将它们从内容中剥离并保留其余部分。

研究 XSS 预防。

https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet

【讨论】:

【参考方案4】:

JavaScript 是“内联”执行的,即按照它在 DOM 中出现的顺序(如果不是这种情况,你永远无法确定在不同脚本中定义的某些变量在使用时是可见的第一次)。

这意味着理论上你可以在页面的开头有一个脚本(即第一个&lt;script&gt;元素),它会查看DOM并删除&lt;div&gt;中的所有&lt;script&gt;元素和事件处理程序。

但实际情况更复杂:DOM 和脚本加载是异步发生的。这意味着浏览器只保证脚本可以看到 DOM 的 before 部分(即我们示例中的标头)。没有任何保证(这与document.write()有关)。所以你可能会看到下一个脚本标签,也可能看不到。

您可以锁定文档的onload 事件 - 这将确保您获得整个 DOM - 但此时恶意代码可能已经执行。当其他脚本操作 DOM 并在那里添加脚本时,情况会变得更糟。因此,您还必须检查 DOM 的每次更改。

所以@cowls 解决方案(在服务器上过滤)是唯一可以在所有情况下工作的解决方案。

【讨论】:

【参考方案5】:

如果您希望在浏览器中显示 JavaScript 代码:

使用 JavaScript 和 HTML,您必须使用 HTML entities 来显示 JavaScript 代码并避免执行此代码。在这里您可以找到 HTML 实体列表:

http://dev.w3.org/html5/html-author/charref http://www.w3schools.com/html/html_entities.asp

如果您使用服务器端脚本语言(PHP、ASP.NET 等),很可能有一个函数可以转义字符串并将特殊字符转换为 HTML 实体。在 PHP 中,您将使用“htmlspecialchars()”或“htmlentities()”。后者涵盖了所有的 HTML 字符。

如果您希望以一种不错的方式显示您的 JavaScript 代码,请尝试以下代码荧光笔:

http://prismjs.com/ http://alexgorbatchev.com/SyntaxHighlighter/ https://highlightjs.org/

【讨论】:

【参考方案6】:

我有一个理论:

将文档的特定部分包装在 noscript 标记内。 使用 DOM 函数丢弃 noscript 标记内的所有 script 标记,然后解开其内容。

概念证明示例:

window.onload = function() 
    var noscripts = /* _live_ list */ document.getElementsByTagName("noscript"),
        memorydiv = document.createElement("div"),
        scripts = /* _live_ list */ memorydiv.getElementsByTagName("script"),
        i,
        j;
    for (i = noscripts.length - 1; i >= 0; --i) 
        memorydiv.innerHTML = noscripts[i].textContent || noscripts[i].innerText;
        for (j = scripts.length - 1; j >= 0; --j) 
            memorydiv.removeChild(scripts[j]);
        
        while (memorydiv.firstChild) 
            noscripts[i].parentNode.insertBefore(memorydiv.firstChild, noscripts[i]);
        
        noscripts[i].parentNode.removeChild(noscripts[i]);
    
;
body  font: medium/1.5 monospace; 
p, h1  margin: 0; 
<h1>Sample Content</h1>
<p>1. This paragraph is embedded in HTML</p>
<script>document.write('<p style="color: red;">2. This paragraph is generated by JavaScript</p>');</script>
<p>3. This paragraph is embedded in HTML</p>
<h1>Sample Content in No-JavaScript Zone</h1>
<noscript>
    <p>1. This paragraph is embedded in HTML</p>
    <script>document.write('<p style="color: red;">2. This paragraph is generated by JavaScript</p>');</script>
    <p>3. This paragraph is embedded in HTML</p>
</noscript>
<noscript>
    <p>1. This paragraph is embedded in HTML</p>
    <script>document.write('<p style="color: red;">2. This paragraph is generated by JavaScript</p>');</script>
    <p>3. This paragraph is embedded in HTML</p>
</noscript>

【讨论】:

如果 div 中的内容是不受信任的,我想这是给定的问题。他们可以关闭&lt;noscript&gt; 标签,然后注入他们喜欢的内容。 是的,正确的解决方案是解决服务器端的问题。我通过 JavaScript 所做的最好在服务器端完成。【参考方案7】:

如果您想稍后重新启用脚本标签,我的解决方案是破坏浏览器环境,以便任何运行的脚本都会在相当早的时候抛出错误。但是,它并不完全可靠,因此您不能将其用作安全功能。

如果您尝试访问全局属性,Chrome 会抛出异常。

setTimeout("Math.random()")
// => VM116:25 Uncaught Error: JavaScript Execution Inhibited  

我将覆盖 window 上的所有可重写属性,但您也可以扩展它以破坏其他功能。

window.allowJSExecution = inhibitJavaScriptExecution();
function inhibitJavaScriptExecution()

    var windowProperties = ;
    var Object = window.Object
    var console = window.console
    var Error = window.Error

    function getPropertyDescriptor(object, propertyName)
        var descriptor = Object.getOwnPropertyDescriptor(object, propertyName);
        if (!descriptor) 
            return getPropertyDescriptor(Object.getPrototypeOf(object), propertyName);
        
        return descriptor;
    

    for (var propName in window)
        try 
            windowProperties[propName] = getPropertyDescriptor(window, propName)
            Object.defineProperty(window, propName, 
                get: function()
                    throw Error("JavaScript Execution Inhibited")
                ,
                set: function()
                    throw Error("JavaScript Execution Inhibited")
                ,
                configurable: true
            )
         catch (err) 
    

    return function allowJSExecution()
        for (var propName in window)
            if (!(propName in windowProperties)) 
                delete windowProperties[propName]
            
        

        for (var propName in windowProperties)
            try 
                Object.defineProperty(window, propName, windowProperties[propName])
             catch (err) 
        
    

【讨论】:

以上是关于我可以要求浏览器不在元素中运行 <script> 吗?的主要内容,如果未能解决你的问题,请参考以下文章

运行环境

执行 animate/scrollTop 后浏览器滚动中断

将 html 附加到 jQuery 元素而不在 html 中运行脚本

Java applet不在AdoptOpenJDK中运行

分离元素并追加而不在div中相乘

JQuery:点击无处不在但有些元素