Aloja datos de usuarios de forma segura en aplicaciones web modernas

David Dworken
David Dworken

Muchas aplicaciones web necesitan mostrar contenido controlado por el usuario. Esto puede ser tan simple como entregar imágenes subidas por el usuario (por ejemplo, fotos de perfil) o tan complejo como renderizar HTML controlado por el usuario (por ejemplo, un instructivo de desarrollo web). Esto siempre fue difícil de hacer de forma segura, por lo que trabajamos para encontrar soluciones fáciles pero seguras que se puedan aplicar a la mayoría de los tipos de aplicaciones web.

Soluciones clásicas para aislar contenido no confiable

La solución clásica para publicar contenido controlado por el usuario de forma segura es usar lo que se conoce como dominios de zona de pruebas. La idea básica es que, si el dominio principal de tu aplicación es example.com, puedes publicar todo el contenido no confiable en exampleusercontent.com. Dado que estos dos dominios son multisitios, cualquier contenido malicioso en exampleusercontent.com no puede afectar a example.com.
Este enfoque se puede usar para entregar de forma segura todo tipo de contenido no confiable, incluidas imágenes, descargas y HTML. Si bien puede parecer que no es necesario usar esto para imágenes o descargas, hacerlo ayuda a evitar los riesgos de espionaje de contenido, especialmente en navegadores heredados.
Los dominios de zona de pruebas se usan ampliamente en toda la industria y funcionan bien desde hace mucho tiempo. Sin embargo, tienen dos desventajas importantes:

  • A menudo, las aplicaciones deben restringir el acceso al contenido a un solo usuario, lo que requiere la implementación de la autenticación y la autorización. Dado que los dominios de zona de pruebas no comparten cookies de forma intencional con el dominio principal de la aplicación, esto es muy difícil de hacer de forma segura. Para admitir la autenticación, los sitios deben depender de las URLs de capabilities o establecer cookies de autenticación independientes para el dominio de zona de pruebas. Este segundo método es especialmente problemático en la Web moderna, donde muchos navegadores restringen las cookies entre sitios de forma predeterminada.
  • Si bien el contenido del usuario está aislado del sitio principal, no está aislado del contenido de otros usuarios. Esto crea el riesgo de que el contenido malicioso de los usuarios ataque otros datos en el dominio de la zona de pruebas (por ejemplo, a través de la lectura de datos del mismo origen).

También vale la pena señalar que los dominios de zona de pruebas ayudan a mitigar los riesgos de suplantación de identidad (phishing), ya que los recursos se segmentan claramente en un dominio aislado.

Soluciones modernas para publicar contenido de los usuarios

Con el tiempo, la Web ha evolucionado y ahora existen formas más sencillas y seguras de publicar contenido no confiable. Existen muchos enfoques diferentes, por lo que describiremos dos soluciones que se usan ampliamente en Google.

Enfoque 1: Publicación de contenido de usuarios inactivos

Si un sitio solo necesita entregar contenido de usuario inactivo (es decir, contenido que no es HTML ni JavaScript, por ejemplo, imágenes y descargas), ahora se puede hacer de forma segura sin un dominio de zona de pruebas aislado. Hay dos pasos clave:

  • Siempre establece el encabezado Content-Type en un tipo MIME conocido que admitan todos los navegadores y que garantice que no contenga contenido activo (cuando tengas dudas, application/octet-stream es una opción segura).
  • Además, siempre configura los siguientes encabezados de respuesta para asegurarte de que el navegador aísle completamente la respuesta.
Encabezado de respuesta Purpose

X-Content-Type-Options: nosniff

Evita el espionaje de contenido

Content-Disposition: attachment; filename="download"

Activa una descarga en lugar de renderizar

Content-Security-Policy: sandbox

Coloca el contenido en la zona de pruebas como si se entregara en un dominio independiente.

Content-Security-Policy: default-src ‘none'

Inhabilita la ejecución de JavaScript (y la inclusión de cualquier subrecurso).

Cross-Origin-Resource-Policy: same-site

Impide que la página se incluya en otros sitios

Esta combinación de encabezados garantiza que la aplicación solo pueda cargar la respuesta como un subrecurso o que el usuario pueda descargarla como un archivo. Además, los encabezados proporcionan múltiples capas de protección contra errores del navegador a través del encabezado de zona de pruebas de CSP y la restricción default-src. En general, la configuración que se describió anteriormente proporciona un alto grado de confianza en que las respuestas que se entregan de esta manera no pueden generar vulnerabilidades de inyección o aislamiento.

Defensa en profundidad

Si bien la solución anterior representa una defensa generalmente suficiente contra los XSS, existen varias medidas de endurecimiento adicionales que puedes aplicar para proporcionar capas adicionales de seguridad:

  • Establece un encabezado X-Content-Security-Policy: sandbox para la compatibilidad con IE11.
  • Establece un encabezado Content-Security-Policy: frame-ancestors 'none' para bloquear la incorporación del extremo.
  • Aísla el contenido de usuario de la zona de pruebas en un subdominio de la siguiente manera:
    • Publicar contenido de usuario en un subdominio aislado (p. ej., Google usa dominios como product.usercontent.google.com)
    • Establece Cross-Origin-Opener-Policy: same-origin y Cross-Origin-Embedder-Policy: require-corp para habilitar el aislamiento de origen cruzado.

Enfoque 2: Publicación de contenido de usuarios activos

La entrega segura de contenido activo (por ejemplo, imágenes HTML o SVG) también se puede realizar sin las debilidades del enfoque clásico de dominio de zona de pruebas.
La opción más sencilla es aprovechar el encabezado Content-Security-Policy: sandbox para indicarle al navegador que aísle la respuesta. Si bien actualmente no todos los navegadores web implementan el aislamiento de procesos para documentos de zona de pruebas, es probable que las mejoras continuas en los modelos de procesos del navegador mejoren la separación del contenido de la zona de pruebas de las aplicaciones de incorporación. Si los ataques de SpectreJS y de compromiso del renderizador están fuera de tu modelo de amenazas, es probable que usar la zona de pruebas de CSP sea una solución suficiente.
En Google, desarrollamos una solución que puede aislar por completo el contenido activo no confiable modernizando el concepto de dominios de zona de pruebas. La idea principal es la siguiente:

  • Crea un nuevo dominio de zona de pruebas que se agregue a la lista de sufijos públicos. Por ejemplo, si agregas exampleusercontent.com al PSL, puedes asegurarte de que foo.exampleusercontent.com y bar.exampleusercontent.com sean multisitio y, por lo tanto, estén completamente aislados entre sí.
  • Todas las URLs que coincidan con *.exampleusercontent.com/shim se dirigen a un archivo de compensación estático. Este archivo de compensación contiene un fragmento corto de HTML y JavaScript que escucha el controlador de eventos message y renderiza el contenido que recibe.
  • Para usar esto, el producto crea un iframe o una ventana emergente en $RANDOM_VALUE.exampleusercontent.com/shim y usa postMessage para enviar el contenido no confiable al shim para su renderización.
  • El contenido renderizado se transforma en un blob y se renderiza dentro de un iframe de zona de pruebas.

En comparación con el enfoque clásico de dominio de zona de pruebas, esto garantiza que todo el contenido esté completamente aislado en un sitio único. Además, como la aplicación principal se encarga de recuperar los datos que se renderizarán, ya no es necesario usar URLs de capabilities.

Conclusión

En conjunto, estas dos soluciones permiten migrar de dominios de zona de pruebas clásicas, como googleusercontent.com, a soluciones más seguras que son compatibles con el bloqueo de cookies de terceros. En Google, ya migramos muchos productos para usar estas soluciones y tenemos más migraciones planificadas para el próximo año.