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

Browser Support

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 52.
  • Safari: 15.4.

La secuencia de comandos entre sitios (XSS), la capacidad de inyectar secuencias de comandos maliciosas en una app web, ha sido una de las mayores vulnerabilidades de seguridad web durante más de una década.

La Política de Seguridad del Contenido (CSP) es una capa de seguridad adicional que ayuda a mitigar los ataques 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 agente de usuario para esa página.

En esta página, se explica cómo usar una CSP basada en nonces o hashes para mitigar los ataques de XSS, en lugar de las CSP basadas en listas de entidades permitidas de host de uso frecuente que, a menudo, dejan la página expuesta a ataques de XSS porque se pueden eludir en la mayoría de las configuraciones.

Término clave: Un nonce es un número aleatorio que se usa solo una vez y que puedes usar para marcar una etiqueta <script> como confiable.

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 de confianza.

Una Política de Seguridad del Contenido basada en nonces o hashes suele denominarse CSP estricta. Cuando una aplicación usa una CSP estricta, los atacantes que encuentran fallas de inyección de HTML generalmente no pueden usarlas para obligar al navegador a ejecutar secuencias de comandos maliciosas en un documento vulnerable. Esto se debe a que la CSP estricta solo permite secuencias de comandos con hash o con el valor de 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 que se parece a script-src www.googleapis.com, es probable que no sea eficaz contra los ataques entre sitios. Este tipo de CSP se denomina CSP de lista de entidades permitidas. Requieren mucha personalización y los atacantes pueden eludirlos.

Los CSP estrictos basados en nonces o hashes criptográficos evitan estos problemas.

Estructura de CSP estricta

Una política de seguridad del contenido estricta básica 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&#39;;
  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&#39;;
  base-uri 'none';

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

  • Utiliza nonces 'nonce-{RANDOM}' o hashes 'sha256-{HASHED_INLINE_SCRIPT}' para indicar qué etiquetas <script> confía el desarrollador del sitio para que se ejecuten en el navegador del usuario.
  • Establece '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 de confianza. Esto también desbloquea el uso de la mayoría de los widgets y las bibliotecas de JavaScript de terceros.
  • No se basa en listas de URLs permitidas, por lo que no se ve afectado por las omisiones comunes de la CSP.
  • Bloquea las secuencias de comandos intercaladas no confiables, como los controladores de eventos intercalados o los URIs javascript:.
  • 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 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 nonce o hash.
  2. Copia la CSP de la sección Estructura de CSP estricta y configúrala como un encabezado de respuesta en toda tu aplicación.
  3. Refactoriza las plantillas HTML y el código del cliente para quitar los patrones que no son compatibles con la CSP.
  4. Implementa tu CSP.

Puedes usar la auditoría de Prácticas recomendadas de Lighthouse (v7.3.0 y versiones posteriores con la marca --preset=experimental) durante todo este proceso para verificar si tu sitio tiene una CSP y si es lo suficientemente estricta como para ser eficaz contra los ataques de XSS.

Advertencia del informe de Lighthouse que indica 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 nonces o hashes

Así es como funcionan los dos tipos de CSP estricta:

CSP basada en Nonce

Con una CSP basada en nonce, generas un número aleatorio en el tiempo de ejecución, lo incluyes en tu CSP y lo asocias con cada etiqueta de secuencia de comandos de tu página. Un atacante no puede incluir ni ejecutar una secuencia de comandos maliciosa en tu página, ya que debería adivinar el número aleatorio correcto para esa secuencia de comandos. Esto solo funciona si el número no se puede adivinar y se genera de nuevo en el tiempo de ejecución para cada respuesta.

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

CSP basada en hash

En el caso de 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. Un atacante no puede incluir ni ejecutar una secuencia de comandos maliciosa en tu página, ya que el hash de esa secuencia de comandos debería estar en tu 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 creadas con frameworks como Angular, React o similares, que se publican de forma estática sin renderización del servidor.

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

Cuando configuras una CSP, tienes algunas opciones:

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

Opción A: CSP basado 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';

Genera un nonce para la 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 los ataques XSS si los atacantes no pueden adivinar el valor del nonce. Un nonce de CSP debe cumplir con los siguientes requisitos:

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

Estos son algunos ejemplos de cómo agregar un nonce de CSP en 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(&>quot;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 de nonce aleatorio 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 todos los 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 varios fragmentos de código intercalados, la sintaxis es la siguiente: 'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'.

Carga dinámica de secuencias de comandos de origen

Puedes cargar de forma dinámica secuencias de comandos de terceros con una secuencia de comandos intercalada.

Un ejemplo de cómo insertar tus secuencias de comandos.
Permitido por la 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.hea<d.appen>dChild(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, reemplazando el marcador de posición {HASHED_INLINE_SCRIPT}. Para reducir la cantidad de hashes, puedes combinar todos los scripts intercalados en un solo script. Para ver esto en acción, consulta este ejemplo y su código.
Se bloqueó debido a la CSP
<script src="https://example.org/fo><o.js&qu>o<t;/script
script src="https://exam><ple.org>/bar.js"/script
La CSP bloquea estos secuencias de comandos porque no se agregaron de forma dinámica y no tienen un atributo integrity que coincida con una fuente permitida.

Consideraciones sobre la carga de secuencias de comandos

El ejemplo de secuencia de comandos intercalada agrega s.async = falsepara garantizar que foo se ejecute antes de 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 solo se detiene 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 finalice la descarga del documento. Si quieres que el documento esté listo cuando se ejecuten las secuencias de comandos, espera el evento DOMContentLoaded antes de agregar las secuencias de comandos. Si esto causa un problema de rendimiento porque las secuencias de comandos no comienzan a descargarse lo suficientemente temprano, usa etiquetas de precarga antes en la página.
  • defer = true no hace nada. 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

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

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

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

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

Refactoriza los controladores de eventos intercalados

Permitido por la CSP
<span id="th>ings&quo<t;A t>h<ing./span
script nonce=>"${nonce}"
  document.getElementById('things').addEventL<istener>('click', doThings);
/script
La CSP permite controladores de eventos que se registran con JavaScript.
Se bloqueó debido a la CSP
<span onclick="doThing>s();&quo<t;A t>hing./span
La CSP bloquea los controladores de eventos intercalados.

Refactoriza los URIs de javascript:

Permitido por el CSP
<a id=">;fo<o&>q<uot;foo/a
script nonce=>"${nonce}"
  document.getElementById('foo').addEventList<ener(&#>39;click', linkClicked);
/script
La CSP permite controladores de eventos registrados con JavaScript.
Se bloqueó debido a la CSP
<a href="javascript:linkClick>ed(<)&>quot;foo/a
La CSP bloquea los URI de JavaScript.

Quita eval() de tu código 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ápida.

Si no puedes quitar todos los usos de eval(), puedes establecer una CSP estricta basada en nonce, pero debes usar la palabra clave 'unsafe-eval' de la CSP, 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 estricta:

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

Browser Support

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 52.
  • Safari: 15.4.

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

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

Paso 5: Implementa tu CSP

Después de confirmar que tu CSP no bloquea ningún script legítimo en tu entorno de desarrollo local, puedes implementarlo en la etapa de pruebas y, luego, en tu entorno de producción:

  1. (Opcional) Implementa tu CSP en modo de solo informes con el encabezado Content-Security-Policy-Report-Only. El modo de solo informes es útil para probar un cambio que podría causar problemas, como una nueva CSP en producción, antes de comenzar a aplicar las restricciones de la CSP. En el modo de solo informes, tu CSP no afecta el comportamiento de tu app, pero el navegador sigue generando errores de consola y registros de incumplimientos cuando encuentra patrones incompatibles con tu CSP, por lo que puedes ver qué se habría interrumpido para tus usuarios finales. Para obtener más información, consulta la API de informes.
  2. Cuando tengas la certeza de que tu CSP no dañará tu sitio para los usuarios finales, implementa tu CSP con el encabezado de respuesta Content-Security-Policy. Te recomendamos que configures tu CSP con un encabezado HTTP del servidor, ya que es más seguro que una etiqueta <meta>. Después de completar este paso, tu CSP comenzará a proteger tu app contra los ataques de XSS.

Limitaciones

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

  • Si agregas un nonce a una secuencia de comandos, pero hay una inserción directamente en el cuerpo o en el parámetro src de ese elemento <script>
  • Si hay inyecciones en las ubicaciones de las secuencias de comandos creadas de forma dinámica (document.createElement('script')), incluidas las funciones de biblioteca que crean nodos 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 inserciones de plantillas en aplicaciones antiguas de AngularJS Un atacante que puede inyectar código en una plantilla de AngularJS puede usarla para ejecutar código JavaScript arbitrario.
  • Si la política contiene 'unsafe-eval', se realizan inserciones 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 estos 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 del Contenido: Un lío exitoso entre el refuerzo y la mitigación.

Lecturas adicionales