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


84

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

喜欢:

<div script="false"> ...

它可能作为附加的安全功能很有用。我想要的所有脚本均已加载到文档的特定部分。文档其他部分中不应包含脚本,如果存在,则不应运行它们。


1
不是我知道的。这是评论而不是答案,因为我不能说100%
自由落体者2014年

4
@ chiastic-security您只能在加载DOM之后遍历DOM。到那时,该块中的所有JS都将执行。
Cowls

8
我能想到的最接近的是内容安全策略,您可以在其中根据脚本的来源(也许就是您想要的)来限制脚本。例如,通过指定script-src:"self"您只允许您域中的脚本在页面中运行。如果您有兴趣,请阅读Mike West撰写的有关CSP的文章
Christoph

1
@ chiastic-security在发送标头后,您打算如何放松标头中指定的限制?无论如何,由于CSP默认情况下禁用内联脚本,并且除非列入白名单,否则不会加载远程脚本,为什么还要将其与其他任何内容组合?CSP可能是OP的最佳选择。我希望有人留下CSP答案。
Dagg Nabbit 2014年

6
如果您担心有人向HTML中注入任意块,那么您提出的解决方案将如何防止他们注入a</div>来关闭此DOM元素,然后启动一个新的<div>将不运行脚本的同级兄弟?
Damien_The_Unbeliever 2014年

Answers:


92

是的,您可以:-)答案是:内容安全策略(CSP)。

大多数现代浏览器都支持此标志该标志告诉浏览器仅从受信任的外部文件中加载JavaScript代码,并禁止所有内部JavaScript代码!唯一的缺点是,您不能在整个页面中使用任何内联JavaScript(不仅限于单个<div>)。尽管可以通过动态地将具有不同安全策略的外部文件中的div包括在内来解决,但我不确定。

但是,如果您可以更改站点以从外部JavaScript文件加载所有JavaScript,则可以与此标头一起完全禁用嵌入式JavaScript!

这是一个带有示例的不错的教程:HTML5Rocks教程

如果您可以将服务器配置为发送此HTTP-Header标志,那么世界将是一个更好的地方!


2
+1真的很酷,我不知道存在!(请注意,您的Wiki链接直接指向德语版本)以下是浏览器支持的摘要
BrianH

4
请注意,即使您这样做,在页面上允许未经转义的用户输入仍然不是一个好主意。基本上,这将允许用户将页面更改为所需的外观。即使将所有“内容安全策略”(CSP)设置都设置为最大值(不允许使用内联脚本,样式等),用户仍然可以使用图像src进行GET请求来执行跨站点请求伪造(CSRF)攻击,或者诱使用户进入单击POST请求的表单提交按钮。
Ajedi32

@ Ajedi32当然,您应该始终清除用户输入。但是CSP甚至可以为GET请求(例如图像或CSS)设置策略,它不仅会阻止它们,甚至会通知您的服务器!
Falco 2014年

1
@Falco我阅读了文档,据我了解,您只能将这些请求限制为给定的域,而不能限制为该域上的一组特定子页面。大概您设置的域将与您的站点相同,这意味着您仍然可以接受CSRF攻击。
2014年

3
@Falco是的,基本上这就是StackExchange使用新的Stack Snippets功能进行的操作:blog.stackoverflow.com/2014/09/…如果您正确清理了输入内容,则不需要单独的域。
Ajedi32

13

您可以<script>使用beforescriptexecute事件来阻止由加载的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可以关闭沙盒元素,因此该脚本将放置在外部并运行。

而且这不会阻止诸如此类的事情<img onerror="javascript:alert('foo')" src="//" />


小提琴似乎没有按预期工作。我应该看不到“被阻止”的部分,对吧?
Salman

@SalmanA正确。可能是您的浏览器不支持beforescriptexecute事件。它适用于Firefox。
Oriol 2014年

可能是因为它无法在Chrome中正常运行,并提供了代码段,不过我看到您只是将其转换为代码段:-)
Mark Hurd

beforescriptexecute似乎不被大多数主流浏览器支持,并且将不被支持。 developer.mozilla.org/en-US/docs/Web/Events/beforescriptexecute
Matt Pennington

8

有趣的问题,我认为不可能。但是,即使是这样,听起来也很容易。

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

如果您只想删除<script>标签并允许其他html标签,则只需将其从内容中删除,然后将其余部分保留。

研究XSS预防。

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


7

JavaScript是“内联”执行的,即按照它在DOM中出现的顺序执行(如果不是这样,您将无法确定第一次使用该脚本时在其他脚本中定义的某些变量是可见的) )。

因此,从理论上讲,这意味着您可以在页面开始处有一个脚本(即第一个<script>元素),该脚本可以查看DOM并删除其中的所有<script>元素和事件处理程序<div>

但是实际情况更为复杂:DOM和脚本加载是异步发生的。这意味着浏览器仅保证脚本可以看到DOM之前的DOM部分(即,本示例中的标头)。除了(与相关的document.write())之外,没有任何保证。因此,您可能会看到下一个脚本标签,或者可能没有。

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

因此,@ cowls解决方案(在服务器上进行过滤)是唯一可以在所有情况下都可以使用的解决方案。


1

如果要在浏览器中显示JavaScript代码:

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

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

如果您希望以一种不错的方式显示JavaScript代码,请尝试以下代码突出显示器之一:


1

我有一个理论:

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

概念证明示例:

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中的内容不受信任,我想这就是问题所在。他们可以关闭<noscript>标签,然后注入自己喜欢的标签。
Cowls

是的,正确的解决方案是在服务器端解决此问题。我通过JavaScript执行的操作最好在服务器端完成。
Salman 2014年

0

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

如果您尝试访问全局属性,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) {}
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.