sysnote
News

A shared ChatGPT link could take over accounts via MathLive's virtual keyboard; OpenAI paid a $5,000 bounty

A public ChatGPT share link was enough to run JavaScript as chatgpt.com in the victim's own session, read their session token and pull their recent conversations. Mukul Goyal of the security firm Parameter published the full chain on October 28. OpenAI fixed it on October 26 and paid a $5,000 bug bounty.

In the worst variant, opening the link was the only click. Goyal says the auto-preview version of the exploit worked three times out of three and finished in about 4.5 seconds.

How the chain worked

The chain combined a third-party math editor with ChatGPT's own HTML preview and Content Security Policy:

  1. ChatGPT's interactive graphs load MathLive, an open-source math input library. MathLive's virtual keyboard installs a postMessage listener in the main chatgpt.com page, and its origin check was set to "none", so it accepted messages from any frame.
  2. ChatGPT's HTML Preview runs model-generated HTML in a sandboxed iframe. That sandbox can still post messages to its parent. The attacker's page sent a mathlive#virtual-keyboard-message with action update-setting, carrying a keyboard layout whose markup was arbitrary HTML, followed by show.
  3. MathLive renders layout markup with innerHTML, passing it through MathfieldElement.createHTML, which by default returns the string unchanged rather than sanitizing it.
  4. The injected HTML included an <iframe srcdoc> without a sandbox attribute. Such an iframe inherits the chatgpt.com origin, so script in it ran with the user's session.
  5. ChatGPT's Content Security Policy allowed YouTube's oEmbed endpoint, which supports a JSONP callback parameter. That turned an allowed YouTube URL into a way to execute attacker-chosen JavaScript.

The proof-of-concept payload read the session token from the page's #client-bootstrap element, called /backend-api/conversations to list the last 20 chats, fetched their content, and sent the result out through an image request.

Timeline

Goyal confirmed the path on October 13 and reported it on October 18. OpenAI reproduced it on October 21 and raised the severity from P2 to P1, shipped partial fixes and CSP changes on October 22 and 23, and marked the issue resolved on October 26.

The library default has not changed

OpenAI's fix covers ChatGPT. We checked MathLive itself. On the main branch of arnog/mathlive, whose latest commit is from October 9, the defaults the chain relied on are still in place:

  • the virtual keyboard's originValidator is initialized to "none", and validateOrigin() accepts every origin in that case;
  • the keyboard listens for messages on window.top when it can reach it;
  • update-setting messages can replace the keyboard layouts, and a layer's markup string is inserted into the keyboard's HTML as is;
  • MathfieldElement.createHTML defaults to (x) => x.

These are documented defaults rather than a hidden bug. MathLive's docs recommend a sanitizing createHTML when displaying untrusted content, and originValidator can be set to "same-origin" or a function. But a page that loads MathLive with its defaults and also frames content it does not control has the same shape of exposure ChatGPT had. We found no MathLive advisory or issue about this so far. If you ship MathLive next to untrusted iframes, setting both options is a small change:

MathfieldElement.createHTML = (html) => DOMPurify.sanitize(html);
window.mathVirtualKeyboard.originValidator = "same-origin";

Goyal's write-up has the full payloads and screenshots of each step.

Comments

No comments yet. Be the first.

Write a comment

Comments are published after review.