Las secuencias de comandos entre sitios basadas en el DOM (XSS del DOM) se producen 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 establecedor 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 habitual que los equipos de desarrollo la introduzcan accidentalmente en sus apps. Los Trusted Types te brindan las herramientas para escribir, revisar la seguridad y mantener las aplicaciones libres de vulnerabilidades de XSS del DOM, ya que hacen que las funciones peligrosas de la API web sean seguras de forma predeterminada. Trusted Types está disponible como un polyfill para los navegadores que aún no lo admiten.
Fondo
Durante muchos años, el XSS del DOM fue 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 deben a 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, en la que el código JavaScript llama a funciones peligrosas con contenido controlado por el usuario.
Para evitar el XSS del servidor, no generes HTML concatenando cadenas. En su lugar, usa bibliotecas de plantillas de escape automático contextual seguro, junto con una política de seguridad del contenido basada en nonce para mitigar errores adicionales.
Ahora, los navegadores también pueden ayudar a evitar el XSS basado en DOM del cliente con Trusted Types.
Introducción a la API
Los Trusted Types funcionan bloqueando las siguientes funciones de receptor riesgosas. Es posible que ya conozcas algunas de ellas, ya que los proveedores de navegadores y los frameworks web ya te impiden usar estas funciones por motivos de seguridad.
- Manipulación de secuencias de comandos:
<script src>
y configuración del contenido de texto de los elementos<script>
- Cómo generar HTML a partir de una cadena:
- Ejecución de contenido de complementos:
- Compilación de código JavaScript en tiempo de ejecución:
eval
setTimeout
setInterval
new Function()
Los Trusted Types requieren que proceses los datos antes de pasarlos a estas funciones de receptor. Usar solo una cadena falla porque el navegador no sabe si los datos son confiables:
anElement.innerHTML = location.href;
Para indicar que los datos se procesaron de forma segura, crea un objeto especial: un Trusted Type.
anElement.innerHTML = aTrustedHTML;
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 las verificaciones de seguridad basadas en tipos que se realizan cuando compilas, ejecutas lint o agrupas tu código en 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 o go-csp-collector de código abierto, o bien 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));
Agrega 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, todos los incumplimientos se informan a //my-csp-endpoint.example
, pero el sitio web sigue funcionando. En la próxima sección, se explica cómo funciona //my-csp-endpoint.example
.
Identifica 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 indica que, en https://my.url.example/script.js
de la línea 39, se llamó a innerHTML
con la cadena que comienza con <img src=x
. Esta información debería ayudarte a reducir las partes del código que podrían introducir XSS en el DOM y que deben cambiarse.
Cómo corregir los incumplimientos
Existen varias opciones para corregir un incumplimiento de Trusted Types. Puedes quitar el código infractor, usar una biblioteca, crear una política de Trusted Types o, como último recurso, crear una política predeterminada.
Vuelve a escribir el código infractor
Es posible que el código que no cumple con los requisitos ya no sea necesario 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);
el.innerHTML = '<img src=xyz.jpg>';
Usa una biblioteca
Algunas bibliotecas ya generan Trusted Types que puedes pasar a las funciones de receptor. Por ejemplo, puedes usar DOMPurify para sanear un fragmento de HTML y quitar cargas útiles de XSS.
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});
DOMPurify admite Trusted Types y devuelve HTML saneado encapsulado en un objeto TrustedHTML
para que el navegador no genere un incumplimiento.
Crea una política de Trusted Types
A veces, no puedes quitar el código que causa el incumplimiento, y no hay ninguna biblioteca para sanitizar el valor y crear un Trusted Type para ti. En esos casos, puedes crear un objeto Trusted Type por tu cuenta.
Primero, crea una política. Las políticas son fábricas de Trusted Types que aplican ciertas reglas de seguridad a 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 objetos TrustedHTML
con su función createHTML()
. Las reglas definidas escapan los caracteres <
en HTML para evitar la creación de elementos HTML nuevos.
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; // '<img src=x onerror=alert(1)>'
Usa una política predeterminada
A veces, no puedes cambiar el código infractor, 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 usa una cadena en un receptor que solo acepta Trusted Type.
Cambia a la aplicación de la Política de Seguridad del Contenido
Cuando tu aplicación ya no genere incumplimientos, puedes comenzar a aplicar Trusted Types:
Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
Ahora, sin importar qué tan compleja 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 protegerlo aún más limitando la creación de políticas.