
XSS almacenado de Bloomberg Memray a través de metadatos de línea de comandos sin escapar
XSS almacenado en Bloomberg Memray mediante metadatos de línea de comandos sin escapar
Encontré este problema al revisar Memray, el perfilador de memoria de Python de Bloomberg, con una pregunta simple en mente:
¿Pueden los metadatos de tiempo de ejecución controlados por el atacante cruzar de forma insegura hacia la salida del informe renderizada en el navegador?
En este caso, la respuesta fue sí.
El error estaba en la ruta de generación de informes HTML de Memray, donde los metadatos de la línea de comandos se renderizaban en un informe abierto en el navegador sin escapar. Eso convirtió un campo operativo en un sumidero HTML ejecutable y, finalmente, se convirtió en CVE-2026-32722.
Proyecto: Memray en GitHub
Aviso: GHSA-r5pr-887v-m2w9 / CVE-2026-32722
argv controlado por el atacante → metadata.command_line → sumidero de plantilla HTML sin escapar → marcado sin procesar en el informe generado → ejecución de JavaScript en el navegador
Memray es un perfilador de memoria de Python.
Instrumenta un proceso de Python, registra el comportamiento de asignación y produce informes que ayudan a los desarrolladores a comprender:
Algunos de esos informes se emiten como HTML y se abren en un navegador.
Eso hace que la generación de informes sea un límite de seguridad real.
La pregunta relevante no es si Memray es una "herramienta local".
La pregunta relevante es si los datos controlados por el atacante pueden cruzar de forma insegura hacia la salida renderizada en el navegador.
En este caso, podían.
Mucha gente subestima las herramientas para desarrolladores.
Eso es un error.
Una vez que una herramienta:
hereda los mismos riesgos de codificación de salida que una aplicación web.
Ese fue el problema central aquí.
Este error no estaba en la lógica de perfilado. No estaba en el seguimiento de asignaciones. No estaba en el manejo nativo de memoria.
Era una clásica falla de límite de confianza:
Eso es suficiente para crear una vulnerabilidad real.
No abordé Memray fuzzeando opciones aleatorias de CLI ni persiguiendo caídas del sistema.
El enfoque más sólido era identificar primero la superficie de seguridad de mayor probabilidad.
Para Memray, esa era la generación de informes HTML.
¿Por qué?
Porque la salida HTML introduce un sumidero en el navegador, y los sumideros del navegador convierten errores comunes de metadatos en problemas de seguridad si:
Eso es exactamente lo que sucedió aquí.
El error se reduce a dos líneas.
En:
def get_render_environment() -> jinja2.Environment:
loader = jinja2.PackageLoader("memray.reporters")
env = jinja2.Environment(loader=loader)
el entorno Jinja se crea sin autoescape.
Luego en:
Command line: <code>{{ metadata.command_line }}</code><br>
metadata.command_line se renderiza directamente en HTML.
Esa es toda la vulnerabilidad.
Porque metadata.command_line está influenciado por el atacante.
Memray registra la línea de comandos utilizada para ejecutar el programa perfilado.
Eso significa que los valores controlados por el usuario de argv se conservan como metadatos y luego se insertan en el informe.
Entonces la cadena de explotación es sencilla:
metadata.command_lineEso convierte los metadatos en contenido ejecutable en el navegador.
La distinción importante es la ejecución.
Muchos errores producen HTML malformado. Eso por sí solo no es suficiente.
Aquí, el contenido controlado por el atacante no solo era visible en el código fuente de la página. El navegador lo interpretaba como HTML activo y lo ejecutaba como JavaScript.
Esa es la diferencia entre:
Entonces la pregunta no era:
"¿Puede aparecer HTML en el informe?"
La pregunta real era:
"¿Puede el HTML controlado por el atacante volverse ejecutable cuando se abre el informe?"
La respuesta fue sí.
Mi reproducción inicial usaba un script explícito más un argumento controlado por el atacante:
cat > victim.py <<'PY'
x = [b"A" * 1024 for _ in range(1000)]
print("done")
PY
python -m memray run -o poc.bin victim.py ''
python -m memray flamegraph -o poc.html poc.bin
El HTML generado contenía marcado sin procesar controlado por el atacante:
Command line: <code>~/memray/src/memray/__main__.py run -o poc.bin victim.py </code><br>
Abrir o actualizar el informe generado provocaba la ejecución de JavaScript.
Eso estableció la afirmación central:
Más tarde, durante la divulgación coordinada, el mantenedor simplificó aún más la reproducción:
python -m memray run -o poc.bin -c '# '
Esa versión es mejor porque aísla el límite vulnerable de forma más directa:
El payload fue intencionalmente simple:
Esto no se trata de payloads llamativos.
Es una sonda de ejecución limpia porque:
Para esta clase de errores, eso es suficiente.
Un solo tipo de informe ya habría sido suficiente para justificar el problema.
Pero quería saber si esto estaba aislado o era estructural.
Confirmé el mismo comportamiento en:
--no-webEso importaba por dos razones.
Mostró que el sumidero vulnerable se reutilizaba en múltiples salidas HTML.
Demostró que el error no dependía de recursos externos alojados en CDN.
--no-web seguía reproduciendo el problema, lo que significa que el fallo estaba en el HTML generado por el propio Memray y en el manejo de plantillas, no en el comportamiento de JS remoto.
Eso hizo que el caso fuera mucho más sólido.
La principal preocupación del mantenedor era el control práctico del atacante.
Eso es justo.
Este no es el tipo de problema donde un atacante remoto no autenticado golpea un endpoint HTTP expuesto y obtiene impacto inmediato.
La condición de explotación es más estrecha:
Entonces la clasificación realista era severidad baja.
Eso no lo convierte en un error débil.
La severidad trata sobre las condiciones de explotación y el impacto probable. La validez trata sobre si el problema es real.
Este problema era claramente real:
Por eso aun así se convirtió en un CVE.
La corrección fue mínima y correcta.
El mantenedor cambió:
{{ metadata.command_line }}
a:
{{ metadata.command_line|e }}
Esa es la corrección adecuada porque aborda directamente el sumidero vulnerable.
En lugar de emitir marcado sin procesar como:
la plantilla ahora emite texto escapado:
<img src=x onerror=alert(1)>
Eso preserva el valor informativo del campo de línea de comandos mientras elimina la ruta de ejecución en el navegador.
El mantenedor también revisó el resto del contexto de la plantilla y concluyó que:
|tojson
Así que el problema se acotó correctamente a metadata.command_line.Ese es exactamente el tipo de revisión de corrección que quieres en una divulgación real.
Esto se reportó de forma privada a través de GitHub Security Advisories.
Los mantenedores:
Al problema se le asignó: CVE-2026-32722
La lección clave aquí es simple:
los metadatos no son automáticamente confiables solo porque parecen operativos.
Nada de eso importa una vez que el contenido controlado por el atacante cruza hacia la salida renderizada en el navegador sin escapar.
En el momento en que una herramienta emite HTML, debe tratarse como una aplicación que produce HTML.
Esa es la verdadera conclusión.
Esta vulnerabilidad no se trataba de un payload inteligente.
Se trataba de identificar el límite correcto.
Memray tomó metadatos de línea de comandos influenciados por el atacante y los renderizó en HTML generado sin escaparlos. El navegador hizo el resto.
Por eso esto se convirtió en CVE-2026-32722.
Corregido en Memray 1.19.2.
