Juega de forma segura en IFrames de zona de pruebas

Crear una experiencia completa en la web actual es casi inevitablemente o bien incorporar componentes y contenido sobre los que no tienes control real. Los widgets de terceros pueden impulsar la participación y desempeñar un papel fundamental en el entorno la experiencia del usuario y, a veces, el contenido generado por usuarios que el contenido nativo de un sitio. Abstenerse de ellos no es una opción, pero aumentan el riesgo de que ocurra algo maloTM en su sitio. Cada widget que incorporas (cada anuncio, cada widget de redes sociales) es un potencial vector de ataque para aquellos con intenciones maliciosas:

Política de Seguridad del Contenido (CSP) puede mitigar los riesgos asociados con ambos tipos de contenido al brindar la posibilidad de incluir en la lista blanca fuentes de secuencias de comandos y otros recursos contenido. Este es un paso importante en la dirección correcta, pero vale la pena destacar la protección que ofrecen la mayoría de las directivas de CSP es binaria: el recurso se está permitido o no lo es. A veces sería útil decir "No soy seguro confío en esta fuente de contenido, pero es muuuy bonita. Incorporarla por favor, navegador, pero que no permita que dañe mi sitio".

Privilegio mínimo

Básicamente, estamos buscando un mecanismo que nos permita otorgar contenido que incorpora solo el nivel mínimo de capacidad necesario para hacer su trabajo. Si un widget no necesita que abra una ventana nueva, por lo que no se puede quitar el acceso a window.open daño. Si no requiere Flash, desactivar la compatibilidad con complementos no debería ser una tarea problema. Somos lo más seguros posible si seguimos el principio de privilegio y bloquea todas y cada una de las funciones que no son directamente relevantes para la funcionalidad que queremos usar. El resultado es que ya no tenemos que confiar ciegamente en que del contenido incorporado no aprovechará los privilegios que no debería usar. Integra simplemente no tendrán acceso a la funcionalidad.

Los elementos de iframe son el primer paso hacia un buen marco de trabajo para una solución de este tipo. Cargar algún componente que no es de confianza en un iframe proporciona una medida de separación. entre tu aplicación y el contenido que deseas cargar. El contenido enmarcado no tendrá acceso al DOM de tu página ni a los datos que hayas almacenado localmente ni ser capaz de dibujar en posiciones arbitrarias de la página; su alcance está limitado contorno del marco. Sin embargo, la separación no es realmente sólida. La página contenida cuenta con varias opciones para el comportamiento molesto o malicioso: video, complementos y ventanas emergentes son la punta del iceberg.

El atributo sandbox del elemento iframe nos brinda justo lo que necesitamos para reforzar las restricciones del contenido enmarcado. Podemos Indicar al navegador que cargue el contenido de un marco específico en un entorno entorno de desarrollo, lo que permite que solo el subconjunto de capacidades necesarias tu trabajo debe hacer.

Sacude, pero verifica

“Tweet” de Twitter es un excelente ejemplo de una funcionalidad que puede ser más incorporadas de forma segura en el sitio a través de una zona de pruebas. Twitter te permite incorporar botón a través de un iframe con el siguiente código:

<iframe src="https://platform.twitter.com/widgets/tweet_button.html"
        style="border: 0; width:130px; height:20px;"></iframe>

Para descubrir qué podemos bloquear, examinemos con detenimiento las capacidades requiere el botón. El HTML que se carga en el marco ejecuta un poco JavaScript desde los servidores de Twitter, y genera una ventana emergente con un de tweeting cuando se hace clic en ellos. Esa interfaz necesita acceso a los cookies para vincular el tweet a la cuenta correcta, y necesita la capacidad para enviar el formulario de tweeting. Eso es todo. el marco no necesita cargar los complementos, no necesita navegar por la ventana de nivel superior ni de otras funciones. Como no necesita esos privilegios, quítalos a través de la zona de pruebas del contenido del marco.

Las zonas de pruebas funcionan sobre la base de una lista blanca. Primero, quitamos todos permisos y, luego, volver a activar las capacidades individuales agregando marcas específicas a la configuración de la zona de pruebas. Para el widget de Twitter, decidimos habilitar JavaScript, las ventanas emergentes, el envío de formularios y los perfiles de twitter.com cookies. Podemos hacerlo agregando un atributo sandbox a iframe con el siguiente valor:

<iframe sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
    src="https://platform.twitter.com/widgets/tweet_button.html"
    style="border: 0; width:130px; height:20px;"></iframe>

Eso es todo. Le dimos al marco todas las capacidades que requiere, y la navegador le denegará el acceso a los privilegios que no teníamos otorgarlo de forma explícita mediante el valor del atributo sandbox

Control detallado de las funciones

Vimos algunas de las marcas de zona de pruebas posibles en el ejemplo anterior, ahora Explora con más detalle el funcionamiento interno del atributo.

Dado un iframe con un atributo de zona de pruebas vacío, el documento enmarcado se someterá a una zona de pruebas completa y quedará sujeto a las siguientes restricciones:

  • JavaScript no se ejecutará en el documento enmarcado. Esto no solo incluye JavaScript cargado de forma explícita a través de etiquetas de secuencias de comandos, pero también controladores de eventos intercalados y JavaScript: URLs. Esto también significa que el contenido en etiquetas “noscript” se mostrará, como si el usuario mismo haya inhabilitado la secuencia de comandos.
  • El documento enmarcado se carga en un origen único, lo que significa que todo las verificaciones del mismo origen fallarán; los orígenes únicos no coinciden con ningún otro origen, ni incluso ellos mismos. Entre otras consecuencias, esto significa que el documento no tiene acceso a datos almacenados en cualquier cookie de origen o en cualquier otro mecanismo de almacenamiento (almacenamiento del DOM, base de datos indexada, etcétera).
  • El documento enmarcado no puede crear ventanas ni diálogos nuevos (mediante window.open o target="_blank", por ejemplo).
  • No se pueden enviar formularios.
  • No se cargarán los complementos.
  • El documento enmarcado solo puede navegar por sí mismo, no a su elemento superior de nivel superior. Si estableces window.top.location, se arrojará una excepción y, si haces clic en el vínculo con target="_top" no tendrá ningún efecto.
  • Funciones que se activan automáticamente (elementos del formulario enfocados automáticamente, reproducción automática) videos, etc.).
  • No se puede obtener el bloqueo del puntero.
  • El atributo seamless se ignora en iframes que contenga el documento enmarcado.

Es muy draconiano, y un documento que se cargó en un iframe de zona de pruebas completa representa muy poco riesgo. Por supuesto, tampoco puede aportar mucho valor: podrían lograr una zona de pruebas completa para contenido estático, pero la mayoría de las veces, querrás flexibilizar un poco las cosas.

A excepción de los complementos, cada una de estas restricciones se puede anular Agregar una marca al valor del atributo de la zona de pruebas Los documentos de la zona de pruebas ejecutar complementos, ya que estos son código nativo que no están en la zona de pruebas, pero todo lo demás es aceptable. juego:

  • allow-forms permite el envío de formularios.
  • allow-popups permite ventanas emergentes (impactante).
  • allow-pointer-lock permite (sorpresa) el bloqueo del puntero.
  • allow-same-origin permite que el documento mantenga su origen. páginas cargadas de https://example.com/ retendrán el acceso a los datos de ese origen.
  • allow-scripts permite la ejecución de JavaScript y también permite que las funciones se activan automáticamente (ya que serían sencillas de implementar a través de JavaScript).
  • allow-top-navigation permite que el documento salga del marco. navegando por la ventana de nivel superior.

Con esto en mente, podemos evaluar exactamente por qué obtuvimos esa información conjunto de marcas de zona de pruebas en el ejemplo anterior de Twitter:

  • Se requiere allow-scripts, ya que la página cargada en el marco ejecuta algunos JavaScript para manejar la interacción del usuario
  • allow-popups es obligatorio, ya que el botón muestra un formulario de tweeting en un nuevo formulario en la ventana modal.
  • allow-forms es obligatorio, ya que se debe enviar el formulario de tweeting.
  • allow-same-origin es necesario, ya que las cookies de twitter.com, de lo contrario, lo harían. ser inaccesible y el usuario no podía iniciar sesión para publicar el formulario.

Es importante tener en cuenta que las marcas de zona de pruebas aplicadas a un fotograma también se aplicarán a cualquier ventana o marco creado en la zona de pruebas. Esto significa que tenemos para agregar allow-forms a la zona de pruebas del marco, aunque el formulario solo exista en la ventana que aparece el marco.

Una vez implementado el atributo sandbox, el widget obtiene solo los permisos que y las funciones como los complementos, la navegación en la parte superior y el bloqueo del puntero se bloqueó. Redujimos el riesgo de incorporar el widget sin efectos negativos. Todos los involucrados ganan.

Separación de privilegios

Una zona de pruebas de contenido de terceros para ejecutar su código no confiable en un de bajo privilegio es bastante beneficioso. Pero ¿qué sucede con tus código propio? Confías en ti mismo, ¿verdad? Entonces, ¿por qué preocuparse por la zona de pruebas?

Respondería esa pregunta: si tu código no necesita complementos, ¿por qué responderlo? acceso a complementos? En el mejor de los casos, es un privilegio que nunca usas; en el peor, para que los atacantes puedan entrar. El código de todo el mundo tiene errores y prácticamente todas las aplicaciones son vulnerables a la explotación de una manera u otra. Hacer una zona de pruebas de tu propio código significa que, incluso si un atacante logra afecte tu aplicación, no se le otorgará acceso completo al origen de la aplicación; solo podrá hacer cosas que la aplicación podría tareas. Sigue siendo malo, pero no tanto como podría ser.

Puedes reducir aún más el riesgo si divides tu aplicación en piezas lógicas y una zona de pruebas cada una con el mínimo privilegio posible. Esta técnica es muy común en el código nativo: Chrome, por ejemplo, se rompe a sí mismo. en un proceso de navegador con muchos privilegios que tiene acceso al disco duro local y puede realizar conexiones de red, así como muchos procesos de renderizadores con pocos privilegios analizar el contenido que no es de confianza. Los procesadores no necesitan tocar disco, el navegador se encarga de brindar toda la información que necesita representar una página. Incluso si un hacker inteligente encuentra una forma de corromper un renderizador, no está muy lejos, ya que el renderizador no puede hacer mucho por sí mismo. Todos los accesos de alto privilegio deben enrutarse a través del proceso del navegador. Los atacantes tendrán que encontrar varios agujeros en distintas partes del sistema para hacer cualquier daño, lo que reduce en gran medida el riesgo de emprendimiento exitoso.

Zona de pruebas segura de eval()

Con la zona de pruebas y el La API de postMessage, el éxito de este modelo es bastante sencillo de aplicar en la Web. Partes de tu aplicación puede alojarse en iframe de zona de pruebas, y el documento principal puede y mediar entre ellos publicando mensajes y escuchando de respuestas ante incidentes. Este tipo de estructura garantiza que los exploits en cualquier parte del a la app local realice el menor daño posible. También tiene la ventaja de que te obliga a crear puntos de integración claros, para que sepas exactamente dónde debes estar ten cuidado con la validación de entrada y salida. Veamos un ejemplo de juguete, solo para ver cómo podría funcionar.

Evalbox es una app muy interesante que toma una cadena y la evalúa como JavaScript. Guau, ¿verdad? Justo has estado esperando durante todos estos años. Es bastante peligroso aplicación, ya que permitir la ejecución de JavaScript arbitrario significa que cualquier y todos los datos que ofrece el origen estarán listos para usarse. Mitigaremos el riesgo de Ocurren cosas malasTM cuando se garantiza que el código se ejecute dentro de una zona de pruebas. lo que lo hace bastante más seguro. Trabajaremos en el código desde el de adentro hacia afuera, empezando por el contenido del marco:

<!-- frame.html -->
<!DOCTYPE html>
<html>
    <head>
    <title>Evalbox's Frame</title>
    <script>
        window.addEventListener('message', function (e) {
        var mainWindow = e.source;
        var result = '';
        try {
            result = eval(e.data);
        } catch (e) {
            result = 'eval() threw an exception.';
        }
        mainWindow.postMessage(result, event.origin);
        });
    </script>
    </head>
</html>

Dentro del marco, tenemos un documento pequeño que solo escucha mensajes de su elemento superior enganchando al evento message del objeto window. Cuando el elemento superior ejecuta postMessage en el contenido del iframe, este evento activará, lo que nos da acceso a la cadena que nuestro elemento superior desea que ejecutar.

En el controlador, tomamos el atributo source del evento, que es el elemento superior. en la ventana modal. Lo usaremos para enviar de vuelta el resultado de nuestro arduo trabajo listo. Luego, haremos el trabajo pesado, pasando los datos que se nos entregaron eval() Esta llamada se incluyó en un bloque try, ya que operaciones prohibidas dentro de una zona de pruebas, iframe generará excepciones del DOM con frecuencia. vamos a atrapar e informar un mensaje de error amable en su lugar. Por último, publicamos el resultado a la ventana superior. Esto es bastante simple.

El elemento superior es igualmente sencillo. Crearemos una IU pequeña con un textarea para el código y un button para la ejecución, y extraeremos frame.html a través de un iframe en la zona de pruebas, lo que solo permite la ejecución de la secuencia de comandos:

<textarea id='code'></textarea>
<button id='safe'>eval() in a sandboxed frame.</button>
<iframe sandbox='allow-scripts'
        id='sandboxed'
        src='frame.html'></iframe>

Ahora, conectaremos todo para la ejecución. Primero, escucharemos las respuestas de las iframe y las alert() a nuestros usuarios. Presumiblemente, una aplicación real haría algo menos molesto:

window.addEventListener('message',
    function (e) {
        // Sandboxed iframes which lack the 'allow-same-origin'
        // header have "null" rather than a valid origin. This means you still
        // have to be careful about accepting data via the messaging API you
        // create. Check that source, and validate those inputs!
        var frame = document.getElementById('sandboxed');
        if (e.origin === "null" &amp;&amp; e.source === frame.contentWindow)
        alert('Result: ' + e.data);
    });

A continuación, conectaremos un controlador de eventos para que haga clic en el button. Cuando el usuario tomaremos el contenido actual de textarea y lo pasaremos al marco para la ejecución:

function evaluate() {
    var frame = document.getElementById('sandboxed');
    var code = document.getElementById('code').value;
    // Note that we're sending the message to "*", rather than some specific
    // origin. Sandboxed iframes which lack the 'allow-same-origin' header
    // don't have an origin which you can target: you'll have to send to any
    // origin, which might alow some esoteric attacks. Validate your output!
    frame.contentWindow.postMessage(code, '*');
}

document.getElementById('safe').addEventListener('click', evaluate);

Fácil, ¿verdad? Creamos una API de evaluación muy simple y podemos estar seguros de que El código que se evalúa no tiene acceso a información sensible, como las cookies o DOM. Del mismo modo, el código evaluado no puede cargar complementos, mostrar ventanas nuevas o cualquier otra actividad molesta o maliciosa.

Puedes hacer lo mismo con tu propio código si divides las aplicaciones monolíticas en componentes de un solo propósito. Cada uno se puede unir en una API de mensajería simple, solo como lo que hemos escrito anteriormente. La ventana superior con privilegios elevados puede actuar como un controlador y despachador que envían mensajes a módulos específicos, cada uno con el tener la menor cantidad de privilegios posible para hacer su trabajo, escuchar los resultados asegurándote de que cada módulo esté bien alimentado con solo la información que necesita.

Sin embargo, ten en cuenta que debes tener mucho cuidado cuando tratas con contenido enmarcado. que provienen del mismo origen que el elemento superior. Si una página en https://example.com/ enmarca otra página del mismo origen con una zona de pruebas. que incluya las marcas allow-same-origin y allow-scripts, luego la página enmarcada puede alcanzar el elemento superior y eliminar el atributo de zona de pruebas por completo.

Juega en tu zona de pruebas

Las zonas de pruebas están disponibles en varios navegadores: Firefox 17 y versiones posteriores, IE10+ y Chrome al momento de escribir (Caniuse, obviamente, tiene una versión tabla de asistencia). Aplicando sandbox a iframes que incluyas te permite otorgar ciertos privilegios a la el contenido que muestran, solo los privilegios necesarios para el contenido para funcionar correctamente. Esto te da la oportunidad de reducir el riesgo asociados con la inclusión de contenido de terceros, más allá de lo que es posible gracias a la seguridad del contenido Política.

Además, la zona de pruebas es una poderosa técnica para reducir el riesgo de que podrá explotar agujeros en tu propio código. Si separas un elemento aplicación monolítica en un conjunto de servicios de zona de pruebas, cada uno responsable de un una pequeña parte de una funcionalidad autónoma, los atacantes se verán obligados a no solo comprometer fotogramas específicos a su contenido, sino también su controlador. Ese es un una tarea mucho más difícil, especialmente porque el controlador puede reducirse dentro del alcance. Puedes dedicar tu esfuerzo relacionado con la seguridad a auditar ese código si pídele ayuda al navegador con el resto.

Eso no quiere decir que la zona de pruebas sea una solución completa al problema de la seguridad en Internet. Ofrece una defensa en profundidad y, a menos que tengas control sobre las claves de acceso clientes, aún no puedes confiar en la compatibilidad del navegador para todos tus usuarios (si controlas a tus clientes, un entorno empresarial, por ejemplo, ¡hurra!). Algún día... pero, por ahora, la zona de pruebas es otra capa para fortalecer sus defensas, no es una defensa completa contra la cual puedes confiar. Aun así, las capas son excelentes. Te sugiero que aproveches esta uno.

Lecturas adicionales

  • "Separación de privilegios en aplicaciones HTML5" es un artículo interesante que trabaja a través del diseño de un pequeño marco, y su aplicación a tres aplicaciones HTML5 existentes.

  • Las zonas de pruebas pueden ser aún más flexibles cuando se combinan con otros dos iframes nuevos. atributos: srcdoc, y seamless. El primero te permite rellenar un marco con contenido sin la sobrecarga de una solicitud HTTP; esta última permite que el estilo fluya al contenido enmarcado. Por el momento, ambos tipos de navegador tienen un soporte bastante miserable (Chrome y WebKit) noches). pero será una combinación interesante en el futuro. Podrías hacer lo siguiente: por ejemplo, en la zona de pruebas de comentarios de un artículo a través del siguiente código:

        <iframe sandbox seamless
                srcdoc="<p>This is a user's comment!
                           It can't execute script!
                           Hooray for safety!</p>"></iframe>