Skip to main content

Cómo corregir la vulnerabilidad XSS de `marked`

Escrito por

15 de mayo de 2016

0 minutos de lectura

Hace unas semanas agregamos a nuestra base de datos una vulnerabilidad de Cross-Site Scripting (XSS) en el popular paquete marked. En esta publicación explicamos la vulnerabilidad, mostramos cómo explotarla en una aplicación de ejemplo y explicamos cómo corregir el problema en tu aplicación.

marked analiza Markdown y lo convierte en HTML, lo que facilita transformar las entradas de los usuarios —comentarios, reseñas de productos y consultas de soporte— en texto enriquecido (más o menos), con enlaces, negritas, cursivas y más. Como Markdown no admite JavaScript, a menudo se considera inmune a Cross-Site Scripting y, por lo tanto, seguro para mostrar entradas de usuarios.

Sin embargo, en realidad Markdown solo reduce el riesgo de XSS, pero no lo elimina por completo. Esta vulnerabilidad de XSS en marked, que es fácil de explotar, es un ejemplo aleccionador de esa diferencia.

Como mostramos en nuestro informe State of Open Source Security 2019, las vulnerabilidades XSS siguen aumentando. La mayoría de las vulnerabilidades divulgadas se reportan en el ecosistema PHP Packagist, seguido de npm y Maven Central.

La vulnerabilidad

Aunque Markdown no admite scripts, marked (al igual que otros clientes de Markdown) sí admite HTML en línea. El HTML en línea puede incluir etiquetas que los atacantes pueden usar para inyectar scripts maliciosos. Como marked se usa a menudo para volver a mostrar en la página las entradas de los usuarios, sus autores agregaron una opción de seguridad para abordar este caso. El paquete admite la opción sanitize, que detecta HTML y entradas peligrosas, y las codifica o elimina.

Aunque sanitize está desactivada de forma predeterminada (lamentablemente), puedes activarla en tu aplicación. Este ejemplo muestra cómo funciona la opción sanitize:

var marked = require('marked');
console.log(marked('<script>alert(1)</script>'));
// Outputs: <script>alert(1)</script>

marked.setOptions({sanitize: true});
console.log(marked('<script>alert(1)</script>'));
// Outputs: <p><script>alert(1)</script></p>

Detectar HTML es importante, pero la sanitización no termina ahí. Aunque Markdown no admite scripts, sí admite enlaces, lo que permite crear enlaces javascript (por ejemplo, javascript:alert(1)) que pueden causar daños cuando alguien hace clic en ellos. La función sanitize lo tiene en cuenta y elimina los enlaces que parecen javascript:. Incluso elimina enlaces que usan la entidad HTML de dos puntos, : (por ejemplo, javascript&58;alert(1)). Lamentablemente, aun con esta protección, se le escapa un caso…

HTML es un formato muy flexible y los navegadores son muy tolerantes al procesarlo. Un ejemplo de esta tolerancia es que, al procesar entidades HTML, los navegadores no exigen los dos puntos finales y aceptan tanto : como :. En cambio, la sanitización de marked exige los dos puntos y trata el texto como texto simple si no los encuentra. Esto significa que se eliminará :, pero &58this; simplemente se enviará a la salida. Un atacante puede usar esta técnica para evadir marked y lograr que los navegadores ejecuten un script.

Este ejemplo de código muestra dónde funciona y dónde no funciona sanitize:

var marked = require('marked');
marked.setOptions({sanitize: true});

// Naive attempt - fails.
console.log(marked('[Gotcha](javascript:alert(1))'));
// Outputs: <p>)</p>

// Evasion attempt using '&#58;' instead of ':' - fails.
console.log(marked('[Gotcha](javascript:alert(1))'));
// Outputs: <p></p>

// Evasion attempt using '&#58;' (note the 'this') instead of ':' - SUCCEEDS
console.log(marked('[Gotcha](javascript:alert(1))'));
// Outputs: <p><a href="javascript&#58this;alert(1&#41;)">Gotcha</a></p>
// Same as: <p><a href="javascript:this;alert(1);">Gotcha</a></p>

El navegador interpretará : igual que : y ejecutará el script al hacer clic. Por supuesto, el script que incluimos no tiene mayor utilidad, pero un atacante podría inyectar una carga útil mucho más sofisticada, vulnerar la política del mismo origen del navegador y provocar todos los daños que puede causar XSS.

Explotación en vivo en Goof

Al igual que hicimos al analizar la vulnerabilidad relacionada con Buffer de mongoose, agregamos esta vulnerabilidad a nuestra aplicación vulnerable, Goof. Creemos que explotar una vulnerabilidad y ver el código vulnerable permite comprender mejor el problema. Puedes clonar Goof y ponerlo en funcionamiento siguiendo las instrucciones en GitHub.

Goof es una aplicación de tareas pendientes que usa marked para admitir Markdown en sus notas. Goof es una aplicación de tareas pendientes de primera categoría, ¡y una aplicación así simplemente DEBE admitir enlaces, negritas y cursivas!

Por ejemplo, al ingresar las tareas Buy **beer** y [snyk](https://snyk.io/), se obtendrían las negritas y el hipervínculo esperados, así:

Lista de tareas de Goof junto a las herramientas para desarrolladores del navegador, que inspeccionan enlaces HTML, incluido un enlace visible a “snyk”.

Ahora, probemos ingresar una carga útil maliciosa. La siguiente captura de pantalla muestra el estado visual y del DOM después de ingresar cada una de las tres cargas útiles de ataque anteriores. Ten en cuenta que, como se trata de una lista de tareas, la primera que se ingresa aparece al final (en tercer lugar) de la lista.

Como puedes ver, el sanitizador redujo los dos elementos inferiores, que muestran los dos primeros intentos de ataque, a <p></p> y <p>)</p>. Sin embargo, la carga útil de más arriba creó correctamente un hipervínculo que ejecutará javascript:this;alert(1). Ejecutar this no hace nada (simplemente hace referencia a una variable existente), mientras que la alerta muestra una ventana emergente.

Después de hacer que la alerta de nuestra explotación fuera más clara y hacer clic en el enlace, obtenemos esto:

Una alerta del navegador en la página Goof TODO muestra «exploit de marked exitoso» con un botón Aceptar.

Puedes probar este flujo de ataque por tu cuenta si instalas Goof localmente y revisas las cargas útiles de explotación en el directorio exploits.

Cómo corregir el problema

Algo inusual es que no existe una versión oficial de marked que corrija el problema. El repositorio de marked ha estado inactivo desde el verano pasado y la vulnerabilidad se divulgó tiempo después.

Sin embargo, puedes corregir el problema fácilmente aplicando un parche con Wizard de Snyk. Nuestro equipo de investigación de seguridad creó este parche a partir del pull request original que Matt Austin envió al repositorio.

Como todos los demás parches de Snyk, puedes consultar los archivos detallados del parche en nuestra base de datos de vulnerabilidades de código abierto. De hecho, hay 3 parches distintos para diferentes versiones de marked; el más sencillo no es más que esto:

Diferencia de código que muestra una expresión regular revisada para buscar entidades HTML decimales, hexadecimales y con nombre.
  • Actualización: Los responsables de marked finalmente publicaron una nueva versión de marked (v0.3.6) el 30 de julio de 2016, que corrige este problema.

Como alternativa, puedes considerar usar otro paquete de Markdown, como markdown-it o remarkable. No hay garantía de que estén libres de vulnerabilidades, pero actualmente no se conocen vulnerabilidades sin corregir en ellos.

Nueva publicación, vieja vulnerabilidad

Un último aspecto interesante de esta vulnerabilidad es que, en realidad, es bastante antigua. El problema se reportó en mayo de 2015, pero las bases de datos de vulnerabilidades no lo registraron hasta el mes pasado. Este tipo de demoras no es raro, ya que el volumen de problemas en GitHub es increíblemente alto y es difícil mantenerse al día.

Si encuentras un problema de seguridad reportado en un paquete npm, esté corregido o no, avísanos en security@snyk.io y lo revisaremos para agregarlo a nuestra base de datos. El enorme tamaño del ecosistema npm exige que trabajemos en conjunto para mantenernos al tanto de estos problemas y ayudarnos a mantener la seguridad.

Empieza con Capture the Flag

Aprende a resolver desafíos de Capture the Flag viendo nuestro taller virtual introductorio a pedido.