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

Krzysztof Kotowicz
Krzysztof Kotowicz

Navegadores compatibles

  • 83
  • 83
  • x
  • x

Origen

La secuencia de comandos entre sitios basada en DOM (XSS del DOM) ocurre cuando los datos de una fuente controlada por el usuario (como un nombre de usuario o una URL de redireccionamiento tomada del fragmento de URL) llegan a un receptor, que es una función como eval() o un método set de propiedades como .innerHTML que puede ejecutar código JavaScript arbitrario.

La XSS del DOM es una de las vulnerabilidades de seguridad web más comunes y es común que los equipos de desarrollo la ingresen accidentalmente en sus apps. Trusted Types te brinda las herramientas para escribir, revisar la seguridad y mantener las aplicaciones libres de vulnerabilidades de XSS del DOM mediante la seguridad de las funciones peligrosas de la API web de forma predeterminada. Trusted Types están disponibles como polyfill para los navegadores que aún no los admiten.

Información general

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

Existen dos tipos de secuencias de comandos entre sitios. Algunas vulnerabilidades de XSS se producen por un código del servidor que crea de forma insegura el código HTML que forma el sitio web. Otros tienen una causa raíz en el cliente: el código JavaScript llama a funciones peligrosas con contenido controlado por el usuario.

Para evitar la XSS del servidor, no generes HTML mediante la concatenación de strings. En su lugar, usa bibliotecas seguras de plantillas de escape automático contextual, junto con una Política de Seguridad del Contenido basada en nonce para mitigar errores adicionales.

Ahora, los navegadores también pueden ayudar a prevenir la XSS basado en DOM del cliente mediante Trusted Types.

Introducción a la API

Trusted Types funciona bloqueando las siguientes funciones riesgosas de receptor. Es posible que ya reconozcas algunas de ellas, porque los proveedores del navegador y los frameworks web ya evitan estas funciones por motivos de seguridad.

Trusted Types requiere que proceses los datos antes de pasarlos a estas funciones receptoras. Usar solo una cadena falla porque el navegador no sabe si los datos son confiables:

Lo que no debes hacer
anElement.innerHTML  = location.href;
Cuando se habilita Trusted Types, el navegador arroja un TypeError y evita el uso de un receptor de XSS del DOM con una cadena.

Para indicar que los datos se procesaron de forma segura, crea un objeto especial: un tipo de confianza.

anElement.innerHTML = aTrustedHTML;
  
Cuando Trusted Types está habilitado, el navegador acepta un objeto TrustedHTML para los receptores que esperan fragmentos de HTML. También hay objetos TrustedScript y TrustedScriptURL para otros receptores sensibles.

Trusted Types reduce significativamente la superficie de ataque de XSS del DOM de tu aplicación. Simplifica las revisiones de seguridad y te permite aplicar de manera forzosa los controles de seguridad basados en el tipo que se realizan al compilar, analizar con lint o agrupar tu código durante el tiempo de ejecución en el navegador.

Cómo usar Trusted Types

Prepárate para los informes de incumplimiento de la Política de Seguridad del Contenido

Puedes implementar un recopilador de informes, como reporting-api-processor de código abierto o go-csp-collector, o usar uno de los equivalentes comerciales. También puedes agregar registros personalizados y depurar incumplimientos en el navegador con un ReportingObserver:

const observer = new ReportingObserver((reports, observer) => {
    for (const report of reports) {
        if (report.type !== 'csp-violation' ||
            report.body.effectiveDirective !== 'require-trusted-types-for') {
            continue;
        }

        const violation = report.body;
        console.log('Trusted Types Violation:', violation);

        // ... (rest of your logging and reporting logic)
    }
}, { buffered: true });

observer.observe();

o agregando un objeto de escucha de eventos:

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

Agregar un encabezado de CSP de solo 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, todos los incumplimientos se informan a //my-csp-endpoint.example, pero el sitio web sigue funcionando. En la siguiente sección, se explica cómo funciona //my-csp-endpoint.example.

Cómo identificar los incumplimientos de Trusted Types

A partir de ahora, cada vez que Trusted Types detecte un incumplimiento, el navegador 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, en la línea 39, se llamó a innerHTML con la cadena que comienza con <img src=x. Esta información debería ayudarte a limitar las partes del código que podrían estar introduciendo una XSS del DOM y deben cambiarse.

Corrija los incumplimientos

Existen dos opciones para corregir una infracción de tipo de confianza. Puedes quitar el código infractor, usar una biblioteca, crear una política de tipo de confianza o, como último recurso, crear una política predeterminada.

Vuelve a escribir el código infractor

Es posible que ya no se necesite el código no conforme o que se pueda reescribir sin las funciones que causan los incumplimientos:

el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);
Lo que no debes hacer
el.innerHTML = '<img src=xyz.jpg>';

Cómo usar una biblioteca

Algunas bibliotecas ya generan Trusted Types que puedes pasar a las funciones del receptor. Por ejemplo, puedes usar DOMPurify para limpiar un fragmento HTML y quitar las cargas útiles de XSS.

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

DOMPurify admite Trusted Types y muestra HTML depurado unido a un objeto TrustedHTML para que el navegador no genere un incumplimiento.

Crea una política de tipo de confianza

A veces, no puedes quitar el código que causa la infracción y no hay una biblioteca que limpie el valor y cree un tipo de confianza por ti. En esos casos, puedes crear tú mismo un objeto de tipo de confianza.

Primero, crea una política. Las políticas son fábricas de Trusted Types que aplican ciertas reglas de seguridad en su entrada:

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

Este código crea una política llamada myEscapePolicy que puede producir objetos TrustedHTML mediante su función createHTML(). Las reglas definidas son caracteres < de escape HTML para evitar la creación de nuevos elementos HTML.

Usa la política de la siguiente manera:

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

Usa una política predeterminada

A veces, no puedes cambiar el código ofensivo, por ejemplo, si cargas una biblioteca de terceros desde una CDN. En ese caso, usa 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 llamada default se usa siempre que se use una string en un receptor que solo acepte un tipo de confianza.

Cambiar a la aplicación de la Política de Seguridad del Contenido

Cuando tu aplicación ya no produzca incumplimientos, puedes comenzar a aplicar Trusted Types:

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

Sin importar lo compleja que sea tu aplicación web, lo único que puede introducir una vulnerabilidad de XSS del DOM es el código de una de tus políticas, y puedes limitar la creación de políticas para bloquearlo aún más.

Lecturas adicionales