Mitiga la secuencia de comandos entre sitios (XSS) con una Política de Seguridad del Contenido (CSP) estricta

Navegadores compatibles

  • 52
  • 79
  • 52
  • 15.4

Origen

La secuencia de comandos entre sitios (XSS), la capacidad de insertar secuencias de comandos maliciosas en una aplicación web, ha sido una de las vulnerabilidades de seguridad web más grandes durante más de una década.

La Política de Seguridad del Contenido (CSP) es una capa adicional de seguridad que ayuda a mitigar el XSS. Para configurar una CSP, agrega el encabezado HTTP Content-Security-Policy a una página web y establece valores que controlen qué recursos puede cargar el usuario-agente para esa página.

En esta página, se explica cómo usar una CSP basada en nonces o hash para mitigar XSS, en lugar de los CSP basados en listas de entidades permitidas del host de uso general que a menudo dejan la página expuesta a XSS porque se pueden omitir en la mayoría de los parámetros de configuración.

Término clave: Un nonce es un número al azar que se usa solo una vez y que puedes emplear para marcar una etiqueta <script> como de confianza.

Término clave: Una función hash es una función matemática que convierte un valor de entrada en un valor numérico comprimido llamado hash. Puedes usar un hash (por ejemplo, SHA-256) para marcar una etiqueta <script> intercalada como confiable.

Una Política de Seguridad del Contenido basada en nonces o hashes por lo general se denomina CSP estricta. Cuando una aplicación usa una CSP estricta, los atacantes que encuentran fallas de inserción de HTML por lo general no pueden usarlas para forzar al navegador a ejecutar secuencias de comandos maliciosas en un documento vulnerable. Esto se debe a que una CSP estricta solo permite secuencias de comandos con hash o secuencias de comandos con el valor nonce correcto generado en el servidor, por lo que los atacantes no pueden ejecutar la secuencia de comandos sin conocer el nonce correcto para una respuesta determinada.

¿Por qué deberías usar una CSP estricta?

Si tu sitio ya tiene una CSP similar a script-src www.googleapis.com, es probable que no sea eficaz en contra de varios sitios. Este tipo de CSP se denomina CSP de lista de entidades permitidas. Requieren mucha personalización y los atacantes pueden evitarlas.

Las CSP estrictas basadas en nonces o hashes criptográficos evitan estas dificultades.

Estructura estricta de la CSP

Una Política de Seguridad del Contenido básica y estricta usa uno de los siguientes encabezados de respuesta HTTP:

CSP estricta basada en nonce

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
Cómo funciona una CSP estricta basada en nonce.

CSP estricta basada en hash

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Las siguientes propiedades hacen que una CSP como esta sea "estricta" y, por lo tanto, segura:

  • Usa nonces 'nonce-{RANDOM}' o hash 'sha256-{HASHED_INLINE_SCRIPT}' para indicar qué etiquetas <script> confía el desarrollador del sitio para que se ejecuten en el navegador del usuario.
  • Configura 'strict-dynamic' para reducir el esfuerzo de implementar una CSP basada en hash o nonce, ya que permite automáticamente la ejecución de secuencias de comandos que crea una secuencia de comandos confiable. Esto también desbloquea el uso de la mayoría de las bibliotecas y los widgets de JavaScript de terceros.
  • No se basa en listas de URLs permitidas, por lo que no se ve afectada por omisiones comunes de la CSP.
  • Bloquea secuencias de comandos intercaladas que no son de confianza, como controladores de eventos intercalados o URIs de javascript:.
  • Además, restringe object-src para inhabilitar complementos peligrosos, como Flash.
  • Restringe base-uri para bloquear la inserción de etiquetas <base>. Esto evita que los atacantes cambien las ubicaciones de las secuencias de comandos cargadas desde las URLs relativas.

Adopta una CSP estricta

Para adoptar una CSP estricta, debes hacer lo siguiente:

  1. Decide si tu aplicación debe establecer una CSP basada en hash o nonce.
  2. Copia la CSP de la sección Estructura estricta de la CSP y configúrala como un encabezado de respuesta en toda la aplicación.
  3. Refactoriza las plantillas HTML y el código del cliente para quitar patrones que no sean compatibles con CSP.
  4. Implementa tu CSP.

Puedes usar Lighthouse (7.3.0 y versiones posteriores con la marca --preset=experimental) auditar las prácticas recomendadas durante todo este proceso para verificar si tu sitio tiene una CSP y si es lo suficientemente estricta para ser eficaz contra XSS.

El informe de Lighthouse advierte que no se encontró ninguna CSP en el modo de aplicación forzosa.
Si tu sitio no tiene una CSP, Lighthouse muestra esta advertencia.

Paso 1: Decide si necesitas una CSP basada en hash o nonce

A continuación, se muestra cómo funcionan los dos tipos de CSP estrictas:

CSP basada en nonce

Con una CSP basada en nonce, generas un número al azar en el tiempo de ejecución, lo incluyes en el CSP y lo asocias con cada etiqueta de secuencia de comandos de tu página. Los atacantes no pueden incluir ni ejecutar una secuencia de comandos maliciosa en tu página, ya que tendría que adivinar el número al azar correcto de esa secuencia. Esto solo funciona si no es posible adivinar el número y se genera recién en el tiempo de ejecución para cada respuesta.

Usa una CSP basada en nonce para páginas HTML renderizadas en el servidor. En estas páginas, puedes crear un nuevo número al azar para cada respuesta.

CSP basada en hash

Para una CSP basada en hash, el hash de cada etiqueta de secuencia de comandos intercalada se agrega a la CSP. Cada secuencia de comandos tiene un hash diferente. Los atacantes no pueden incluir ni ejecutar una secuencia de comandos maliciosa en tu página, ya que el hash de esa secuencia tendría que estar en la CSP para que se ejecute.

Usa una CSP basada en hash para las páginas HTML que se entregan de forma estática o las páginas que deben almacenarse en caché. Por ejemplo, puedes usar una CSP basada en hash para aplicaciones web de una sola página compiladas con frameworks, como Angular, React y otros, que se entregan estáticamente sin renderización del servidor.

Paso 2: Establece una CSP estricta y prepara tus secuencias de comandos

Cuando configuras una CSP, tienes las siguientes opciones:

  • Modo de solo informes (Content-Security-Policy-Report-Only) o modo de aplicación (Content-Security-Policy). En el modo de solo informes, el CSP aún no bloqueará los recursos, por lo que no hay fallas en tu sitio, pero puedes ver los errores y obtener informes de todo lo que se habría bloqueado. A nivel local, cuando configuras el CSP, esto no es importante, ya que ambos modos muestran los errores en la consola del navegador. En todo caso, el modo de aplicación puede ayudarte a encontrar los recursos que el borrador de la CSP bloquea, ya que el bloqueo de un recurso puede hacer que la página se vea dañada. El modo de solo informes se vuelve más útil más adelante en el proceso (consulta el Paso 5).
  • Es la etiqueta HTML o del encabezado <meta>. Para el desarrollo local, una etiqueta <meta> puede ser más conveniente para modificar tu CSP y ver rápidamente cómo afecta a tu sitio. Sin embargo, ten en cuenta lo siguiente:
    • Más adelante, cuando implementes la CSP en producción, te recomendamos que la configures como un encabezado HTTP.
    • Si deseas configurar tu CSP en modo de solo informes, deberás establecerla como encabezado, ya que las metaetiquetas de CSP no admiten el modo de solo informes.

Opción A: CSP basada en nonce

Configura el siguiente encabezado de respuesta HTTP Content-Security-Policy en tu aplicación:

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Cómo generar un nonce para CSP

Un nonce es un número aleatorio que se usa solo una vez por carga de página. Una CSP basada en nonce solo puede mitigar el XSS si los atacantes no pueden adivinar el valor del nonce. Un nonce de la CSP debe cumplir con los siguientes requisitos:

  • Un valor aleatorio criptográficamente seguro (idealmente, más de 128 bits de longitud)
  • Se genera de nuevo para cada respuesta
  • Codificación en base64

Estos son algunos ejemplos de cómo agregar un nonce de CSP en los frameworks del servidor:

const app = express();

app.get('/', function(request, response) {
  // Generate a new random nonce value for every response.
  const nonce = crypto.randomBytes(16).toString("base64");

  // Set the strict nonce-based CSP response header
  const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`;
  response.set("Content-Security-Policy", csp);

  // Every <script> tag in your application should set the `nonce` attribute to this value.
  response.render(template, { nonce: nonce });
});

Agrega un atributo nonce a los elementos <script>

Con una CSP basada en nonce, cada elemento <script> debe tener un atributo nonce que coincida con el valor aleatorio del nonce especificado en el encabezado de la CSP. Todas las secuencias de comandos pueden tener el mismo nonce. El primer paso es agregar estos atributos a todas las secuencias de comandos para que la CSP los permita.

Opción B: Encabezado de respuesta de la CSP basado en hash

Configura el siguiente encabezado de respuesta HTTP Content-Security-Policy en tu aplicación:

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Para varias secuencias de comandos intercaladas, la sintaxis es la siguiente: 'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'.

Carga las secuencias de comandos de origen de forma dinámica

Debido a que los hashes de la CSP son compatibles con los navegadores solo para secuencias de comandos intercaladas, debes cargar todas las secuencias de comandos de terceros de forma dinámica con una secuencia de comandos intercalada. Los hashes para secuencias de comandos de origen no son muy compatibles con todos los navegadores.

Ejemplo de cómo intercalar tus secuencias de comandos
Permitida por CSP
<script>
  var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js'];

  scripts.forEach(function(scriptUrl) {
    var s = document.createElement('script');
    s.src = scriptUrl;
    s.async = false; // to preserve execution order
    document.head.appendChild(s);
  });
</script>
Para permitir que se ejecute esta secuencia de comandos, debes calcular el hash de la secuencia de comandos intercalada y agregarlo al encabezado de respuesta de la CSP para reemplazar el marcador de posición {HASHED_INLINE_SCRIPT}. Para reducir la cantidad de hashes, puedes combinar todas las secuencias de comandos intercaladas en una sola. Para ver esto en acción, consulta este ejemplo y su código.
Bloqueado por CSP
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
La CSP bloquea estas secuencias de comandos porque solo las secuencias de comandos intercaladas pueden generar un hash para el hash.

Consideraciones sobre la carga de secuencias de comandos

En el ejemplo de la secuencia de comandos intercalada, se agrega s.async = false para garantizar que foo se ejecute antes que bar, incluso si bar se carga primero. En este fragmento, s.async = false no bloquea el analizador mientras se cargan las secuencias de comandos, ya que estas se agregan de forma dinámica. El analizador se detiene solo mientras se ejecutan las secuencias de comandos, como lo haría para las secuencias de comandos async. Sin embargo, con este fragmento, ten en cuenta lo siguiente:

  • Es posible que una o ambas secuencias de comandos se ejecuten antes de que el documento termine de descargarse. Si deseas que el documento esté listo cuando se ejecuten las secuencias de comandos, espera al evento DOMContentLoaded antes de adjuntarlas. Si esto genera un problema de rendimiento porque las secuencias de comandos no comienzan a descargarse con suficiente anticipación, usa etiquetas de precarga antes en la página.
  • defer = true no realizará ninguna acción. Si necesitas ese comportamiento, ejecuta la secuencia de comandos de forma manual cuando sea necesario.

Paso 3: Refactoriza las plantillas HTML y el código del cliente

Los controladores de eventos intercalados (como onclick="…" y onerror="…") y los URI de JavaScript (<a href="javascript:…">) se pueden usar para ejecutar secuencias de comandos. Eso significa que un atacante que encuentre un error de XSS puede insertar este tipo de HTML y ejecutar JavaScript malicioso. Una CSP basada en hash o nonce prohíbe el uso de este tipo de lenguaje de marcado. Si tu sitio usa alguno de estos patrones, tendrás que refactorizarlos en alternativas más seguras.

Si habilitaste la CSP en el paso anterior, podrás ver los incumplimientos de CSP en la consola cada vez que la CSP bloquee un patrón incompatible.

Informes de incumplimiento de CSP en la consola para desarrolladores de Chrome
Errores de la consola relacionados con código bloqueado.

En la mayoría de los casos, la solución es sencilla:

Refactoriza controladores de eventos intercalados

Permitida por CSP
<span id="things">A thing.</span>
<script nonce="${nonce}">
  document.getElementById('things').addEventListener('click', doThings);
</script>
CSP permite controladores de eventos que están registrados con JavaScript.
Bloqueado por CSP
<span onclick="doThings();">A thing.</span>
La CSP bloquea los controladores de eventos intercalados.

Refactoriza los URIs de javascript:

Permitida por CSP
<a id="foo">foo</a>
<script nonce="${nonce}">
  document.getElementById('foo').addEventListener('click', linkClicked);
</script>
CSP permite controladores de eventos que están registrados con JavaScript.
Bloqueado por CSP
<a href="javascript:linkClicked()">foo</a>
La CSP bloquea el código JavaScript: URIs.

Quitar eval() de tu JavaScript

Si tu aplicación usa eval() para convertir serializaciones de cadenas JSON en objetos JS, debes refactorizar esas instancias a JSON.parse(), que también es más rápido.

Si no puedes quitar todos los usos de eval(), puedes establecer una CSP estricta basada en nonce, pero debes usar la palabra clave de CSP 'unsafe-eval', lo que hace que tu política sea un poco menos segura.

Puedes encontrar estos y más ejemplos de refactorización en este codelab de CSP estricto:

Paso 4 (opcional): Agrega resguardos para admitir versiones anteriores del navegador

Navegadores compatibles

  • 52
  • 79
  • 52
  • 15.4

Origen

Si necesitas admitir versiones anteriores del navegador, haz lo siguiente:

  • El uso de strict-dynamic requiere agregar https: como resguardo para versiones anteriores de Safari. Cuando lo hagas, ocurrirá lo siguiente:
    • Todos los navegadores que admiten strict-dynamic ignoran el resguardo https:, por lo que esto no reducirá la seguridad de la política.
    • En navegadores antiguos, las secuencias de comandos de fuentes externas solo pueden cargarse si provienen de un origen HTTPS. Esto es menos seguro que una CSP estricta, pero evita algunas causas comunes de XSS, como las inyecciones de URIs de javascript:.
  • Para garantizar la compatibilidad con versiones muy antiguas del navegador (más de 4 años), puedes agregar unsafe-inline como resguardo. Todos los navegadores recientes ignoran unsafe-inline si hay un nonce o hash de CSP.
Content-Security-Policy:
  script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
  object-src 'none';
  base-uri 'none';

Paso 5: Implementa tu CSP

Después de confirmar que la CSP no bloquea ninguna secuencia de comandos legítima en el entorno de desarrollo local, puedes implementar el CSP en la etapa de pruebas y, luego, en el entorno de producción:

  1. Implementa tu CSP en modo de solo informes con el encabezado Content-Security-Policy-Report-Only (opcional). El modo de solo informes es útil para probar un cambio potencialmente rotundo, como una CSP nueva en producción, antes de comenzar a aplicar restricciones de CSP. En el modo de solo informes, tu CSP no afecta el comportamiento de la app, pero el navegador aún genera informes de incumplimientos y errores de la consola cuando encuentra patrones incompatibles con tu CSP, por lo que puedes ver lo que habría fallado para los usuarios finales. Para obtener más información, consulta API de Reporting.
  2. Cuando estés seguro de que tu CSP no generará fallas en tu sitio para los usuarios finales, implementa tu CSP con el encabezado de respuesta Content-Security-Policy. Recomendamos configurar tu CSP con un encabezado HTTP del servidor porque es más segura que una etiqueta <meta>. Después de completar este paso, tu CSP comenzará a proteger tu app contra XSS.

Limitaciones

En general, una CSP estricta proporciona una capa adicional de seguridad sólida que ayuda a mitigar el XSS. En la mayoría de los casos, la CSP reduce la superficie de ataque de manera significativa, ya que rechaza patrones peligrosos, como los URIs javascript:. Sin embargo, según el tipo de CSP que uses (nonces, hashes, con o sin 'strict-dynamic'), hay casos en los que la CSP no protege la app:

  • Si nonces una secuencia de comandos, pero hay una inyección directamente en el cuerpo o el parámetro src de ese elemento <script>
  • Si hay inyecciones en las ubicaciones de secuencias de comandos creadas de forma dinámica (document.createElement('script')), incluidas las funciones de la biblioteca que crean nodos del DOM script según los valores de sus argumentos Esto incluye algunas APIs comunes, como .html() de jQuery, así como .get() y .post() en jQuery < 3.0.
  • Si hay inyecciones de plantillas en aplicaciones antiguas de AngularJS. Un atacante que puede insertar contenido en una plantilla de AngularJS puede usarla para ejecutar JavaScript arbitrario.
  • Si la política contiene 'unsafe-eval', inyecciones en eval(), setTimeout() y algunas otras APIs que se usan con poca frecuencia.

Los desarrolladores y los ingenieros de seguridad deben prestar especial atención a esos patrones durante las revisiones de código y las auditorías de seguridad. Puedes encontrar más detalles sobre estos casos en Política de seguridad de contenido: un conflicto entre el endurecimiento y la mitigación.

Lecturas adicionales