Kann ich einen Browser bitten, <script> s nicht innerhalb eines Elements auszuführen?

84

Ist es möglich, Browsern anzuweisen, kein JavaScript aus bestimmten Teilen eines HTML-Dokuments auszuführen?

Mögen:

<div script="false"> ...

Dies kann als zusätzliches Sicherheitsmerkmal nützlich sein. Alle gewünschten Skripte werden in einen bestimmten Teil des Dokuments geladen. In anderen Teilen des Dokuments sollten keine Skripte vorhanden sein, und falls vorhanden, sollten sie nicht ausgeführt werden.

Seppo Erviälä
quelle
1
Nicht dass ich wüsste. Dies ist eher ein Kommentar als eine Antwort, weil ich nicht 100% sagen kann
Freifaller
4
@ chiastic-security Sie können das DOM erst durchlaufen, nachdem das DOM geladen wurde. Zu diesem Zeitpunkt hätte jeder JS in diesem Block ausgeführt.
Hauben
8
Das nächste, an das ich denken kann, ist die Inhaltssicherheitsrichtlinie, in der Sie Skripte nach ihrer Herkunft einschränken können (vielleicht ist es das, was Sie wollen). Wenn script-src:"self"Sie beispielsweise angeben , dass nur Skripts aus Ihrer Domain auf der Seite ausgeführt werden dürfen. Wenn Sie interessiert sind, lesen Sie diesen Artikel von Mike West über CSP .
Christoph
1
@ chiastic-security Wie wollen Sie eine in einem Header angegebene Einschränkung lockern, nachdem die Header bereits gesendet wurden? Da CSP Inline-Skripte standardmäßig deaktiviert und Remote-Skripte nur in der Whitelist geladen werden, warum sollten Sie sie dann mit anderen Elementen kombinieren? CSP ist wahrscheinlich die beste Wahl für das OP. Ich hoffe, jemand hinterlässt eine CSP-Antwort.
Dagg Nabbit
6
Wenn Sie sich Sorgen machen, dass jemand beliebige Blöcke in Ihren HTML-Code einfügt, wie kann Ihre vorgeschlagene Lösung verhindern, dass er ein </div>Element einfügt , um dieses DOM-Element zu schließen, und dann ein neues Element startet <div>, das ein Geschwister desjenigen ist, in dem keine Skripte ausgeführt werden?
Damien_The_Unbeliever

Antworten:

92

JA, Sie können :-) Die Antwort lautet: Content Security Policy (CSP).

Die meisten modernen Browser unterstützen dieses Flag , das den Browser anweist, nur JavaScript-Code aus einer vertrauenswürdigen externen Datei zu laden und den gesamten internen JavaScript-Code nicht zuzulassen! Der einzige Nachteil ist, dass Sie kein Inline-JavaScript auf Ihrer gesamten Seite verwenden können (nicht nur für eine einzelne <div>). Es könnte zwar eine Problemumgehung geben, indem das div aus einer externen Datei mit einer anderen Sicherheitsrichtlinie dynamisch eingefügt wird, aber da bin ich mir nicht sicher.

Wenn Sie jedoch Ihre Site so ändern können, dass das gesamte JavaScript aus externen JavaScript-Dateien geladen wird, können Sie Inline-JavaScript mit diesem Header vollständig deaktivieren!

Hier ist ein schönes Tutorial mit Beispiel: HTML5Rocks Tutorial

Wenn Sie den Server so konfigurieren können, dass er dieses HTTP-Header-Flag sendet, ist die Welt ein besserer Ort!

Falco
quelle
2
+1 Das ist wirklich cool, ich hatte keine Ahnung, dass es das gibt! (Ein Hinweis, Ihr Wiki-Link führt direkt zur deutschen Version) Hier ist der Überblick über die Browser-Unterstützung: caniuse.com/#feat=contentsecuritypolicy
BrianH
4
Beachten Sie, dass es auch dann eine schlechte Idee ist, uneingeschränkte Benutzereingaben auf einer Seite zuzulassen, wenn Sie dies tun. Dies würde es dem Benutzer grundsätzlich ermöglichen, die Seite so zu ändern, wie sie möchte. Selbst wenn alle CSP-Einstellungen (Content Security Policy) auf Maximum eingestellt sind (Inline-Skripte, -Stile usw. sind nicht zulässig), kann der Benutzer dennoch einen CSRF-Angriff (Cross-Site Request Forgery) mit Image-srcs für GET-Anforderungen ausführen oder den Benutzer dazu verleiten Klicken Sie auf eine Schaltfläche zum Senden eines Formulars für POST-Anfragen.
Ajedi32
@ Ajedi32 Natürlich sollten Sie Benutzereingaben immer bereinigen. CSP kann jedoch auch Richtlinien für GET-Anforderungen wie Bilder oder CSS festlegen. Es blockiert diese nicht nur, sondern informiert sogar Ihren Server darüber!
Falco
1
@Falco Ich habe die Dokumente gelesen und nach meinem Verständnis können Sie diese Anforderungen nur auf eine bestimmte Domäne beschränken, nicht auf eine bestimmte Gruppe von Unterseiten in der Domäne. Vermutlich ist die von Ihnen festgelegte Domain dieselbe wie Ihre Site, was bedeutet, dass Sie weiterhin offen für CSRF-Angriffe sind.
Ajedi32
3
@Falco Ja, genau das hat StackExchange mit der neuen Funktion "Stack Snippets" getan: blog.stackoverflow.com/2014/09/… Wenn Sie die Eingabe jedoch ordnungsgemäß bereinigen , ist keine separate Domain erforderlich.
Ajedi32
13

Sie können das von geladene JavaScript <script>mit dem folgenden beforescriptexecuteEreignis blockieren :

<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>

Beachten Sie, dass dies beforescriptexecutein HTML 5.0 definiert wurde, aber in HTML 5.1 entfernt wurde. Firefox ist der einzige große Browser, der es implementiert hat.

Wenn Sie eine nicht vertrauenswürdige Menge von HTML in Ihre Seite einfügen, beachten Sie, dass das Blockieren von Skripten in diesem Element keine größere Sicherheit bietet, da nicht vertrauenswürdiges HTML das Sandbox-Element schließen kann und das Skript daher außerhalb platziert und ausgeführt wird.

Und das wird Dinge wie nicht blockieren <img onerror="javascript:alert('foo')" src="//" />.

Oriol
quelle
Die Geige scheint nicht wie erwartet zu funktionieren. Ich sollte den "blockierten" Teil nicht sehen können, oder?
Salman A
@ SalmanA Genau. Wahrscheinlich unterstützt Ihr Browser kein beforescriptexecuteEreignis. Es funktioniert unter Firefox.
Oriol
Wahrscheinlich, weil es in Chrome nicht funktioniert, mit dem bereitgestellten Snippet, obwohl ich sehe, dass Sie es gerade erst in ein Snippet konvertiert haben :-)
Mark Hurd
beforescriptexecuteEs wird anscheinend nicht unterstützt und wird von den meisten gängigen Browsern nicht unterstützt. developer.mozilla.org/en-US/docs/Web/Events/beforescriptexecute
Matt Pennington
8

Interessante Frage, ich denke nicht, dass es möglich ist. Aber selbst wenn es so ist, klingt es so, als wäre es ein Hack.

Wenn der Inhalt dieses Div nicht vertrauenswürdig ist, müssen Sie die Daten auf der Serverseite maskieren, bevor sie in der HTTP-Antwort gesendet und im Browser gerendert werden.

Wenn Sie nur <script>Tags entfernen und andere HTML-Tags zulassen möchten, entfernen Sie sie einfach aus dem Inhalt und lassen Sie den Rest.

Untersuchen Sie die XSS-Prävention.

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

Hauben
quelle
7

JavaScript wird "inline" ausgeführt, dh in der Reihenfolge, in der es im DOM angezeigt wird (wenn dies nicht der Fall wäre, könnten Sie nie sicher sein, dass eine in einem anderen Skript definierte Variable sichtbar war, als Sie es zum ersten Mal verwendeten ).

Das bedeutet also, dass Sie theoretisch am Anfang der Seite ein Skript haben könnten (dh das erste <script>Element), das das DOM durchsucht und alle <script>Elemente und Ereignishandler in Ihrem entfernt <div>.

Die Realität ist jedoch komplexer: Das Laden von DOMs und Skripten erfolgt asynchron. Dies bedeutet, dass der Browser nur garantiert, dass ein Skript den Teil des DOM sehen kann, der sich davor befindet (dh der bisherige Header in unserem Beispiel). Es gibt keine Garantie für etwas anderes (dies hängt damit zusammen document.write()). Vielleicht sehen Sie das nächste Skript-Tag oder nicht.

Sie könnten sich an das onloadEreignis des Dokuments halten - was sicherstellen würde, dass Sie das gesamte DOM erhalten -, aber zu diesem Zeitpunkt könnte bereits bösartiger Code ausgeführt worden sein. Es wird schlimmer, wenn andere Skripte das DOM manipulieren und dort Skripte hinzufügen. Sie müssten also auch nach jeder Änderung des DOM suchen.

Die @ cowls-Lösung (Filterung auf dem Server) ist also die einzige Lösung, die in allen Situationen funktioniert.

Aaron Digulla
quelle
1

Wenn Sie JavaScript-Code in Ihrem Browser anzeigen möchten:

Bei Verwendung von JavaScript und HTML müssen Sie HTML-Entitäten verwenden , um den JavaScript-Code anzuzeigen und zu vermeiden, dass dieser Code ausgeführt wird. Hier finden Sie die Liste der HTML-Entitäten:

Wenn Sie eine serverseitige Skriptsprache (PHP, ASP.NET usw.) verwenden, gibt es höchstwahrscheinlich eine Funktion, die einer Zeichenfolge entgeht und die Sonderzeichen in HTML-Entitäten konvertiert. In PHP würden Sie "htmlspecialchars ()" oder "htmlentities ()" verwenden. Letzteres deckt alle HTML-Zeichen ab.

Wenn Sie Ihren JavaScript-Code auf schöne Weise anzeigen möchten, probieren Sie einen der Code-Textmarker aus:

Wissam El-Kik
quelle
1

Ich habe eine Theorie:

  • Wickeln Sie den bestimmten Teil des Dokuments in ein noscriptTag ein.
  • Verwenden Sie DOM-Funktionen, um alle scriptTags innerhalb des noscriptTags zu verwerfen und den Inhalt zu entpacken.

Beispiel für einen Proof of Concept:

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>

Salman A.
quelle
Wenn der Inhalt in der div nicht vertrauenswürdig ist, was ich denke, wird die Frage gestellt. Sie könnten einfach das <noscript>Etikett schließen und dann injizieren, was sie mögen.
Hauben
Ja, die richtige Lösung besteht darin, das Problem auf der Serverseite zu beheben. Was ich über JavaScript mache, sollte besser auf der Serverseite gemacht werden.
Salman A
0

Wenn Sie Skript-Tags später wieder aktivieren möchten, bestand meine Lösung darin, die Browserumgebung zu beschädigen, sodass jedes Skript, das ausgeführt wird, ziemlich früh einen Fehler auslöst. Es ist jedoch nicht absolut zuverlässig, sodass Sie es nicht als Sicherheitsfunktion verwenden können.

Wenn Sie versuchen, auf globale Eigenschaften zuzugreifen, löst Chrome eine Ausnahme aus.

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

Ich überschreibe alle überschreibbaren Eigenschaften auf window, aber Sie können es auch erweitern, um andere Funktionen zu unterbrechen.

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) {}
        }
    }
}
Matt Zeunert
quelle