Evita las vulnerabilidades de secuencias de comandos en sitios cruzados basadas en DOM con Trusted Types

Reduce la superficie de ataque DOM XSS de tu aplicación.

Krzysztof Kotowicz
Krzysztof Kotowicz

¿Por qué debería preocuparte?

El cross-site scripting (secuencias de comandos en sitios cruzados) basado en DOM (DOM XSS) es una de las vulnerabilidades de la seguridad web más comunes y fáciles de introducir en tu aplicación. Trusted Types te brinda las herramientas para escribir, revisar la seguridad y mantener las aplicaciones libres de vulnerabilidades DOM XSS haciendo que las peligrosas funciones de la API web sean seguras de manera predeterminada. Trusted Types es compatible con Chrome 83 y cuenta con un polyfill disponible para otros navegadores. Consulta Compatibilidad con navegadores para obtener información actualizada sobre compatibilidad con varios navegadores.

Antecedentes

Durante muchos años, DOM XSS ha sido una de las vulnerabilidades de seguridad web más frecuentes y peligrosas.

Hay dos grupos distintos de secuencias de comandos entre sitios. Algunas vulnerabilidades XSS son causadas por el código del lado del servidor que crea de manera insegura el código HTML que forma el sitio web. Otras tienen su origen en el cliente, donde el código JavaScript llama a funciones peligrosas con contenido controlado por el usuario.

Para evitar XSS del lado del servidor, no generes HTML concatenando cadenas y utiliza bibliotecas de plantillas seguras con escape automático contextual. Usa una Política de seguridad de contenido basada en nonce para una mitigación adicional contra los errores, ya que inevitablemente se producen.

Ahora, un navegador también puede ayudar a prevenir los XSS del lado del cliente (también conocidos como basados en DOM) con Trusted Types.

Introducción a la API

Trusted Types funciona bloqueando las siguientes funciones de sumidero de riesgo. Es posible que ya reconozcas algunas de ellas, ya que los proveedores de navegadores y los marcos web ya te alejan del uso de estas funciones por razones de seguridad.

Trusted Types requiere que se procesen los datos antes de pasarlos a las funciones de sumidero mencionadas. El simple hecho de usar una cadena fallará, ya que el navegador no sabe si los datos son confiables:

No hagas
anElement.innerHTML  = location.href;

Con Trusted Types habilitado, el navegador genera un TypeError y evita el uso de un receptor DOM XSS con una cadena.

Para indicar que los datos se procesaron de forma segura, crea un objeto especial - un Trusted Types.

Hacer
anElement.innerHTML = aTrustedHTML;

Con Trusted Types habilitado, el navegador acepta un TrustedHTML para receptores que esperan fragmentos de HTML. También hay TrustedScript y TrustedScriptURL para otros receptores sensibles.

Trusted Types reduce considerablemente la superficie de ataque DOM XSS de tu aplicación. Simplifica las revisiones de seguridad y te permite hacer cumplir las verificaciones de seguridad basadas en tipos que se realizan al compilar, enlazar o empaquetar tu código en tiempo de ejecución, en el navegador.

Cómo utilizar Trusted Types

Prepárate para los informes de infracción de la política de seguridad de contenido

Puedes implementar un recopilador de informes (como el de código abierto go-csp-collector) o utilizar uno de los equivalentes comerciales. También puedes depurar las infracciones en el navegador:

window.addEventListener('securitypolicyviolation',
    console.error.bind(console));

Agregar un encabezado de CSP solo para informes

Agrega el siguiente encabezado de respuesta HTTP a los documentos que deseas migrar a Trusted Types.

Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

Ahora todas las infracciones se informan a //my-csp-endpoint.example, pero el sitio web sigue funcionando. La siguiente sección explica cómo funciona //my-csp-endpoint.example

Identificar infracciones de Trusted Types

A partir de ahora, cada vez que Trusted Types detecte una infracción, se enviará un informe a un report-uri configurado. Por ejemplo, cuando tu aplicación pasa una cadena a innerHTML, el navegador envía el siguiente informe:

{
"csp-report": {
    "document-uri": "https://my.url.example",
    "violated-directive": "require-trusted-types-for",
    "disposition": "report",
    "blocked-uri": "trusted-types-sink",
    "line-number": 39,
    "column-number": 12,
    "source-file": "https://my.url.example/script.js",
    "status-code": 0,
    "script-sample": "Element innerHTML <img src=x"
}
}

Esto dice que en https://my.url.example/script.js la línea 39 innerHTML fue llamado con la cadena que comienza con <img src=x. Esta información debería ayudarte a delimitar qué partes del código pueden estar introduciendo DOM XSS y deben cambiar.

Resolver las infracciones

Hay un par de opciones para corregir una infracción de Trusted Type. Puedes eliminar el código infractor, utilizar una biblioteca, crear una política de Trusted Type o, como último recurso, crear una política predeterminada.

Vuelve a escribir el código infractor

¿Quizás la funcionalidad no conforme ya no sea necesaria o se pueda reescribir de una manera moderna sin usar las funciones propensas a errores?

No hagas
el.innerHTML = '';
Hacer
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);

Usa una biblioteca

Algunas bibliotecas ya generan tipos de confianza que puede pasar a las funciones de receptor. Por ejemplo, puedes usar DOMPurify para desinfectar un fragmento de HTML, eliminando cargas útiles XSS.

import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});

DOMPurify admite Trusted Types y devolverá HTML desinfectado envuelto en un TrustedHTML de manera que el navegador no genere una infracción.

Crea una política de Trusted Type

A veces no es posible eliminar la funcionalidad y no existe una biblioteca para desinfectar el valor y crear un tipo de confianza para ti. En esos casos, crea para ti mismo un objeto Trusted Type.

Para eso, primero crea una política. Las políticas son fábricas de tipos de confianza que imponen ciertas reglas de seguridad en sus entradas:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
    createHTML: string => string.replace(/\</g, '<')
  });
}

Este código crea una política llamada myEscapePolicy que puede producir TrustedHTML través de su función createHTML(). Las reglas definidas con HTML-escape < caracteres evitarán la creación de nuevos elementos HTML.

Utiliza la política de esta manera:

const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML);  // true
el.innerHTML = escaped;  // '<img src=x onerror=alert(1)>'

Utiliza una política predeterminada

A veces no se puede cambiar el código infractor. Por ejemplo, este es el caso si está cargando una biblioteca de terceros desde una CDN. En ese caso, utilice una política predeterminada:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  trustedTypes.createPolicy('default', {
    createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
  });
}

La política con un nombre default se usa siempre que se usa una cadena en un receptor que solo acepta Trusted Type.

Pasa a aplicar la política de seguridad de contenidos

Cuando su aplicación ya no produzca infracciones, puede comenzar a hacer cumplir los tipos de confianza:

Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

¡Ya está! Ahora, no importa cuán compleja sea tu aplicación web, lo único que puede introducir una vulnerabilidad DOM XSS es el código en una de tus políticas, y puedes bloquearlo aún más limitando la creación de políticas.

Otras lecturas