¿Puedo pedirle a un navegador que no ejecute <script> s dentro de un elemento?


84

¿Es posible decirle a los navegadores que no ejecuten JavaScript desde partes específicas de un documento HTML?

Me gusta:

<div script="false"> ...

Podría ser útil como característica de seguridad adicional. Todos los scripts que quiero se cargan en una parte específica del documento. No debe haber secuencias de comandos en otras partes del documento y, si las hay, no deben ejecutarse.


1
No que yo supiese. Esto es un comentario más que una respuesta, porque no puedo decir 100%
freefaller

4
@ chiastic-security Solo puede atravesar el DOM después de que se cargue el DOM. En qué punto cualquier JS en ese bloque se habría ejecutado.
Capuchas

8
Lo más cercano que se me ocurre es la política de seguridad del contenido, donde puede restringir los scripts por su origen (quizás eso es lo que desea). Por ejemplo, al especificar script-src:"self"que permite que solo se ejecuten scripts de su dominio en la página. Si está interesado, lea este artículo de Mike West sobre CSP .
Christoph

1
@ chiastic-security, ¿cómo planea relajar una restricción especificada en un encabezado después de que los encabezados ya se hayan enviado? De todos modos, dado que CSP deshabilita los scripts en línea de forma predeterminada, y los scripts remotos no se cargarán a menos que estén en la lista blanca, ¿por qué necesitaría combinarlos con cualquier otra cosa? La CSP es probablemente la mejor apuesta del OP; Espero que alguien deje una respuesta CSP.
Dagg Nabbit

6
Si le preocupa que alguien inyecte bloques arbitrarios en su HTML, ¿cómo va a evitar su solución propuesta que inyecte un </div>para cerrar este elemento DOM y luego comenzar uno nuevo <div>que será hermano de aquel en el que los scripts no se están ejecutando?
Damien_The_Unbeliever

Respuestas:


92

SÍ, puede :-) La respuesta es: Política de seguridad de contenido (CSP).

La mayoría de los navegadores modernos admiten esta bandera , que le dice al navegador que solo cargue código JavaScript desde un archivo externo confiable y no permita todo el código JavaScript interno. El único inconveniente es que no puede usar JavaScript en línea en toda su página (no solo para una <div>). Aunque podría haber una solución al incluir dinámicamente el div de un archivo externo con una política de seguridad diferente, no estoy seguro de eso.

Pero si puede cambiar su sitio para cargar todo JavaScript desde archivos JavaScript externos, ¡puede deshabilitar JavaScript en línea junto con este encabezado!

Aquí hay un buen tutorial con un ejemplo: HTML5Rocks Tutorial

Si puede configurar el servidor para enviar esta bandera de encabezado HTTP, ¡el mundo será un lugar mejor!


2
+1 Eso es realmente genial, ¡no tenía idea de que existía! (una nota, su enlace wiki es directamente a la versión alemana) Aquí está el resumen de la compatibilidad con el navegador: caniuse.com/#feat=contentsecuritypolicy
BrianH

4
Tenga en cuenta que incluso si hace esto, permitir la entrada del usuario sin escape en una página sigue siendo una mala idea. Básicamente, eso permitiría al usuario cambiar la página para que se vea como quiera. Incluso con todas las configuraciones de la Política de seguridad de contenido (CSP) configuradas al máximo (no permiten scripts en línea, estilos, etc.), el usuario aún podría realizar un ataque de falsificación de solicitud entre sitios (CSRF) utilizando srcs de imagen para solicitudes GET, o engañando al usuario para que haciendo clic en un botón de envío de formulario para solicitudes POST.
Ajedi32

@ Ajedi32 Por supuesto, siempre debe desinfectar las entradas de los usuarios. Pero CSP incluso puede establecer políticas para solicitudes GET como imágenes o css, ¡no solo las bloqueará sino que incluso informará a su servidor al respecto!
Falco

1
@Falco Leí los documentos y entendí que solo se pueden restringir esas solicitudes a un dominio determinado, no a un conjunto específico de subpáginas del dominio. Presumiblemente, el dominio que establezca sería el mismo que su sitio, lo que significa que aún estaría abierto a ataques CSRF.
Ajedi32

3
@Falco Sí, eso es básicamente lo que hizo StackExchange con la nueva función Stack Snippets: blog.stackoverflow.com/2014/09/… Sin embargo, si desinfecta correctamente la entrada, no es necesario un dominio separado.
Ajedi32

13

Puede bloquear JavaScript cargado por <script>, usando beforescriptexecuteevent:

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

Tenga en cuenta que beforescriptexecutese definió en HTML 5.0 pero se ha eliminado en HTML 5.1. Firefox es el único navegador importante que lo implementó.

En caso de que esté insertando un grupo de HTML que no es de confianza en su página, tenga en cuenta que bloquear scripts dentro de ese elemento no proporcionará más seguridad, porque el HTML que no es de confianza puede cerrar el elemento de espacio aislado y, por lo tanto, el script se colocará afuera y se ejecutará.

Y esto no bloqueará cosas como <img onerror="javascript:alert('foo')" src="//" />.


El violín no parece funcionar como se esperaba. No debería poder ver la parte "bloqueada", ¿verdad?
Salman A

@SalmanA Exactamente. Probablemente, su navegador no admita beforescriptexecuteevent. Funciona en Firefox.
Oriol

Probablemente porque no funciona en Chrome, con el fragmento proporcionado, aunque veo que acaba de convertirlo en un fragmento :-)
Mark Hurd

beforescriptexecuteparece que no es compatible y no será compatible con la mayoría de los principales navegadores. developer.mozilla.org/en-US/docs/Web/Events/beforescriptexecute
Matt Pennington

8

Pregunta interesante, no creo que sea posible. Pero incluso si lo es, parece que sería un truco.

Si el contenido de ese div no es de confianza, entonces debe escapar de los datos en el lado del servidor antes de que se envíen en la respuesta HTTP y se muestren en el navegador.

Si solo desea eliminar <script>etiquetas y permitir otras etiquetas html, simplemente elimínelas del contenido y deje el resto.

Analice la prevención de XSS.

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


7

JavaScript se ejecuta "en línea", es decir, en el orden en que aparece en el DOM (si ese no fuera el caso, nunca podría estar seguro de que alguna variable definida en un script diferente estaba visible cuando la usaba por primera vez ).

Eso significa, en teoría, que podría tener un script al principio de la página (es decir, el primer <script>elemento) que mira a través del DOM y elimina todos los <script>elementos y controladores de eventos dentro de su<div> .

Pero la realidad es más compleja: la carga de DOM y script ocurre de forma asincrónica. Esto significa que el navegador solo garantiza que un script pueda ver la parte del DOM que está antes (es decir, el encabezado hasta ahora en nuestro ejemplo). No hay garantías para nada más allá de (esto está relacionado con document.write()). Por lo tanto, es posible que vea la siguiente etiqueta de secuencia de comandos o tal vez no.

Podría aferrarse al onloadevento del documento, lo que aseguraría que obtenga todo el DOM, pero en ese momento, el código malicioso ya podría haberse ejecutado. Las cosas empeoran cuando otros scripts manipulan el DOM y los agregan allí. Por lo tanto, también tendría que verificar cada cambio del DOM.

Así que la solución @cowls (filtrado en el servidor) es la única solución que puede funcionar en todas las situaciones.


1

Si está buscando mostrar código JavaScript en su navegador:

Usando JavaScript y HTML, tendrá que usar entidades HTML para mostrar el código JavaScript y evitar que este código se ejecute. Aquí puede encontrar la lista de entidades HTML:

Si está utilizando un lenguaje de secuencias de comandos del lado del servidor (PHP, ASP.NET, etc.), lo más probable es que haya una función que escaparía de una cadena y convertiría los caracteres especiales en entidades HTML. En PHP, usaría "htmlspecialchars ()" o "htmlentities ()". Este último cubre todos los caracteres HTML.

Si está buscando mostrar su código JavaScript de una manera agradable, pruebe uno de los resaltadores de código:


1

Tengo una teoría:

  • Envuelva la parte específica del documento dentro de una noscriptetiqueta.
  • Utilice las funciones DOM para descartar todas las scriptetiquetas dentro de la noscriptetiqueta y luego desenvuelva su contenido.

Ejemplo de prueba de concepto:

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>


Si el contenido del div no es de confianza, supongo que se le da la pregunta. Podrían simplemente cerrar la <noscript>etiqueta y luego inyectar lo que les gusta.
capuchas

Sí, la solución adecuada es solucionar el problema del lado del servidor. Lo que estoy haciendo a través de JavaScript debería hacerse mejor en el lado del servidor.
Salman A

0

Si desea volver a habilitar las etiquetas de secuencia de comandos más adelante, mi solución fue romper el entorno del navegador para que cualquier secuencia de comandos que se ejecute arroje un error bastante pronto. Sin embargo, no es totalmente confiable, por lo que no puede usarlo como función de seguridad.

Si intenta acceder a las propiedades globales, Chrome generará una excepción.

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

Estoy sobrescribiendo todas las propiedades sobrescribibles window, pero también podría expandirlo para romper otras funciones.

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) {}
        }
    }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.