Pasar al contenido principal
osky
Un link compartido bastaba para tomar cuentas de ChatGPT: la cadena de MathLive, postMessage y YouTube
Sección
Tecnología

Un link a una conversación compartida de ChatGPT bastaba para correr JavaScript ajeno dentro de la sesión de quien lo abría. Así lo documentó la firma de seguridad Parameter el 28 de octubre: armaron una cadena que empezaba en un share público y terminaba con acceso a la API autenticada de la víctima, incluidas sus conversaciones privadas. OpenAI lo corrigió el 26 de octubre, lo clasificó como P1 y pagó 5,000 dólares de recompensa. Si usas ChatGPT, no tienes que hacer nada. Si programas para la web, te toca más de lo que parece: el hueco no estaba en un modelo de IA, sino en un postMessage que no revisaba quién le escribía.

Qué podía hacer el ataque

Según Parameter, el atacante publicaba una conversación maliciosa con la función normal de compartir y mandaba el link. Al abrirla, el código corría como chatgpt.com con la sesión de la víctima. En su prueba controlada, el código listó las conversaciones recientes, descargó el contenido de una conversación privada que nunca se compartió con el atacante y mandó un fragmento a un servidor externo.

La víctima no tenía que instalar nada. En la versión más estable bastaba un clic en Preview; en la final, con el bloque HTML marcado para abrirse solo, abrir el link era el único paso.

Parameter afirma que con eso podía hacer "cualquier acción" en nombre de la cuenta. Lo que demostró, eso sí, fue la lectura de conversaciones; el resto es su deducción de que el código tenía el mismo acceso que la aplicación web. Tampoco reporta que alguien lo haya explotado fuera de su laboratorio.

La cadena, pieza por pieza

Lo interesante es que ninguna pieza estaba rota por sí sola:

  1. La gráfica. Una gráfica interactiva en la conversación cargaba MathLive, una librería de código abierto para capturar fórmulas. Al cargarse, instalaba en la página principal un listener de mensajes para su teclado virtual.
  2. El Preview. ChatGPT corre el HTML de los previews en un frame aislado en otro origen. Ese aislamiento funcionaba: el Preview no podía leer la página. Pero sí podía dejarle mensajes con postMessage.
  3. El listener confiado. El controlador de MathLive aceptaba mensajes de cualquier origen (política none) y permitía cambiar el layout del teclado, que podía traer HTML. Ese HTML terminaba en innerHTML.
  4. El iframe heredero. Con ese HTML, Parameter metió un iframe con srcdoc y sin atributo sandbox. Como lo creaba la página principal, heredaba el origen chatgpt.com.
  5. La CSP. La política de seguridad de contenido bloqueaba scripts inline pero permitía YouTube. El endpoint oEmbed de YouTube devolvía como JavaScript el parámetro callback aunque fuera inválido, y eso dio el script final.

Diagrama de seis pasos: share público, gráfica que carga MathLive, Preview que manda postMessage, HTML sin filtro en innerHTML, iframe sin sandbox con origen chatgpt.com y bypass de CSP con el oEmbed de YouTube

Cómo se arregló

Según Parameter, el reporte llegó al bug bounty de OpenAI el 18 de octubre; el 21 OpenAI lo reprodujo y subió la severidad de P2 a P1; el 23 cambios en la CSP bloquearon parte de la cadena, y el 26 ambos frontends web rechazaban los mensajes de MathLive.

Aparte, el 31 de octubre salió MathLive 0.111.2 con una sección de seguridad: los mensajes del teclado virtual, el proxy y el mathfield ahora validan same-origin por defecto, los mensajes entre ventanas ya no pueden traer layouts personalizados "aunque la validación de origen esté desactivada", y los mensajes que cambian estado quedan atados a la ventana conectada. El changelog no menciona a ChatGPT ni a Parameter, así que no se puede afirmar que una cosa causó la otra. Ojo: la referencia de la API de MathLive todavía documenta none como valor por omisión de originValidator, así que conviene fijarlo explícitamente.

Lo que esto significa si haces web

Mi lectura: esto no es una historia de IA, es de integración. Cualquier sitio con librerías de terceros, iframes y una CSP con excepciones "de confianza" puede tener una cadena parecida. Lo práctico:

  • Si usas MathLive, revisa tu versión con npm ls mathlive, sube a 0.111.2 o más reciente y declara mathVirtualKeyboard.originValidator = 'same-origin' (o una función con tus orígenes permitidos) en lugar de confiar en el default.
  • Busca tus propios listeners: grep -rnE "addEventListener\(.message|onmessage" src/. Cada uno debe revisar event.origin y también event.source. MDN lo dice sin rodeos: no revisarlos "habilita ataques de cross-site scripting".
  • Nunca aceptes HTML como configuración entre ventanas. Manda nombres de una lista fija.
  • Iframes creados dinámicamente, con sandbox, sobre todo si su contenido puede venir de otro frame.
  • Audita las excepciones de tu CSP. Un dominio grande permitido en script-src puede tener un endpoint JSONP que convierta tu política en decorado. Parameter lo resume bien: la CSP es una defensa de respaldo, no la única barrera.

Comparación de dos listeners de postMessage: uno que no revisa origen ni ventana y mete HTML con innerHTML, y otro que valida origen, ventana y una lista fija de layouts

Fuentes