web-dev-qa-db-fra.com

Puis-je demander à un navigateur de ne pas exécuter de <script> dans un élément?

Est-il possible de dire aux navigateurs de ne pas exécuter JavaScript à partir de parties spécifiques d'un document HTML?

Comme:

<div script="false"> ...

Il pourrait être utile en tant que fonction de sécurité supplémentaire. Tous les scripts que je veux sont chargés dans une partie spécifique du document. Il ne doit pas y avoir de scripts dans les autres parties du document et s’ils existent, ils ne doivent pas être exécutés.

81
Seppo Erviälä

OUI, vous pouvez :-) La réponse est: Politique de sécurité du conten (CSP).

La plupart des navigateurs modernes prennent en charge cet indicateur , qui indique au navigateur uniquement de charger le code JavaScript à partir d'un fichier externe de confiance et d'interdire tout le code JavaScript interne! Le seul inconvénient est que vous ne pouvez pas utiliser de code JavaScript intégré dans toute votre page (pas seulement pour un seul <div>). Bien qu'il puisse y avoir une solution de contournement en incluant dynamiquement le div d'un fichier externe avec une politique de sécurité différente, mais je ne suis pas sûr de cela.

Mais si vous pouvez modifier votre site pour charger tout le JavaScript à partir de fichiers JavaScript externes, vous pouvez désactiver complètement le JavaScript en ligne avec cet en-tête!

Voici un joli tutoriel avec un exemple: Tutoriel HTML5Rocks

Si vous pouvez configurer le serveur pour envoyer ce drapeau d'en-tête HTTP, le monde sera un meilleur endroit!

90
Falco

Vous pouvez bloquer JavaScript chargé par <script>, En utilisant l'événement beforescriptexecute:

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

Notez que beforescriptexecute a été défini dans HTML 5.0 mais a été supprimé dans HTML 5.1. Firefox est le seul navigateur majeur à l'avoir implémenté.

Dans le cas où vous insérez un tas de code HTML non approuvé dans votre page, sachez que le blocage des scripts à l'intérieur de cet élément ne fournira pas plus de sécurité, car le code HTML non approuvé peut fermer l'élément en bac à sable, et donc le script sera placé à l'extérieur et exécuté.

Et cela ne bloquera pas des choses comme <img onerror="javascript:alert('foo')" src="//" />.

13
Oriol

Question intéressante, je ne pense pas que ce soit possible. Mais même si c'est le cas, il semble que ce serait un hack.

Si le contenu de ce div n'est pas approuvé, vous devez échapper les données côté serveur avant qu'elles ne soient envoyées dans la réponse HTTP et restituées dans le navigateur.

Si vous souhaitez uniquement supprimer <script> tags et autorisez d'autres tags html, puis supprimez-les du contenu et laissez le reste.

Regardez dans la prévention XSS.

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

8
cowls

JavaScript est exécuté "en ligne", c'est-à-dire dans l'ordre dans lequel il apparaît dans le DOM (si ce n'était pas le cas, vous ne pourriez jamais être sûr qu'une variable définie dans un script différent était visible lors de votre première utilisation) ).

Cela signifie donc qu'en théorie, vous pourriez avoir un script au début de la page (c'est-à-dire le premier élément <script>) Qui examine le DOM et supprime tous les éléments <script> Et les gestionnaires d'événements à l'intérieur de votre <div>.

Mais la réalité est plus complexe: le chargement du DOM et du script se fait de manière asynchrone. Cela signifie que le navigateur garantit uniquement qu'un script peut voir la partie du DOM qui est avant (c'est-à-dire l'en-tête jusqu'à présent dans notre exemple). Il n'y a aucune garantie pour quoi que ce soit au-delà (cela est lié à document.write()). Ainsi, vous pourriez voir la prochaine balise de script ou peut-être pas.

Vous pourriez vous accrocher à l'événement onload du document - ce qui garantirait que vous avez obtenu tout le DOM - mais à ce moment-là, du code malveillant aurait déjà pu être exécuté. Les choses empirent lorsque d'autres scripts manipulent le DOM, y ajoutant des scripts. Vous devez donc également vérifier chaque changement de DOM.

La solution @cowls (filtrage sur le serveur) est donc la seule solution qui puisse fonctionner dans toutes les situations.

7
Aaron Digulla

J'ai une théorie:

  • Enveloppez la partie spécifique du document dans une balise noscript .
  • Utilisez les fonctions DOM pour supprimer toutes les balises script à l'intérieur de la balise noscript, puis dépliez son contenu.

Exemple de preuve de 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>
1
Salman A

Si vous souhaitez afficher le code JavaScript dans votre navigateur:

En utilisant JavaScript et HTML, vous devrez utiliser entités HTML pour afficher le code JavaScript et éviter l'exécution de ce code. Ici vous pouvez trouver la liste des entités HTML:

Si vous utilisez un langage de script côté serveur (PHP, ASP.NET, etc.), il y a très probablement une fonction qui échapperait à une chaîne et convertirait les caractères spéciaux en entités HTML. En PHP, vous utiliseriez "htmlspecialchars ()" ou "htmlentities ()". Ce dernier couvre tous les caractères HTML.

Si vous cherchez à afficher votre code JavaScript de manière agréable, essayez l'un des surligneurs de code:

1
Wissam El-Kik

Si vous souhaitez réactiver les balises de script plus tard, ma solution était de casser l'environnement du navigateur afin que tout script qui s'exécute génère une erreur assez tôt. Cependant, il n'est pas totalement fiable, vous ne pouvez donc pas l'utiliser comme fonctionnalité de sécurité.

Si vous essayez d'accéder aux propriétés globales Chrome lèvera une exception.

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

J'écrase toutes les propriétés écrasables sur window, mais vous pouvez également l'étendre pour casser d'autres fonctionnalités.

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