Stored XSS in draw.io 30.2.4 (CVE-2026-58504) via malicious .drawio file, bypassing the GHSA-fqhg-287p-c6vf fix
The Yokai Team at Hakai Security disclosed CVE-2026-58504 (GHSA-c76x-r78m-phwx, CWE-79), a Stored XSS in draw.io / diagrams.net 30.2.4. It triggers when a victim opens or imports a malicious .drawio file, and the JavaScript runs on the origin of the draw.io instance. In embedded deployments (Confluence, Jira, wikis) that means the origin of the host application. The flaw bypasses the earlier fix for GHSA-fqhg-287p-c6vf (29.7.12) and was fixed in 30.2.5. The article describes a working PoC and session and data theft on a self-hosted instance. It does not report exploitation in the wild.
Entenda a falha
A vulnerabilidade
CVE-2026-58504 (GHSA-c76x-r78m-phwx, CWE-79) is a Stored XSS in draw.io / diagrams.net 30.2.4. The flaw is in the editor's format panel code (mxUtils.canConvertHtmlToSvg). The attacker only needs the victim to open or import a malicious .drawio file, with no authentication on the attacker's side. The JavaScript then runs on the origin of the draw.io instance, which in embedded deployments (Confluence, Jira, wikis) is the host application's origin. It allows session hijacking, authenticated same-origin requests and theft of client-side tokens. It bypasses the earlier fix for GHSA-fqhg-287p-c6vf (29.7.12) and is fixed in 30.2.5.
Como funciona
A .drawio file is XML with cells that carry styles and labels. When it is opened or imported, the cells are auto-selected and TextFormatPanel is rebuilt. The panel decides whether the selection is HTML from an aggregated style that ignores cells marked editable=0. Then TextFormatPanel.addFont() loops over every selected cell, including the non-editable ones, fetches each label with getLabelValue and passes it to canConvertHtmlToSvg. That function puts the label into a div via innerHTML. For a plain-text (html=0) cell, getLabelValue returns the label raw, so attacker-controlled markup is parsed by the browser at that step.
Por que é vulnerável
The root cause is an inconsistency between three individually reasonable decisions, a confused deputy scenario. getLabelValue returns plain-text labels raw, trusting the consumer to escape them. The formatted gate skips editable=0 cells when aggregating style. The feature-detection loop in addFont() does not skip them. As a result, a cell excluded from the gate still reaches innerHTML with no htmlEntities or sanitizeHtml. The earlier fix for GHSA-fqhg-287p-c6vf sanitized the wrong path, so the bug came back through another route. The fix is in 30.2.5; the articles do not detail the change.
Onde está
The sink is mxUtils.canConvertHtmlToSvg() in src/main/webapp/mxgraph/src/util/mxUtils.js, called from TextFormatPanel.addFont() in src/main/webapp/js/grapheditor/Format.js. The article also cites getLabelValue in Graph.js and createSelectionState in EditorUi.js.
Como foi descoberta
The Yokai Team at Hakai Security found it while analyzing the draw.io source code, and published a write-up on 2026-10-27 with the CVE and the GHSA. It is a bypass of the earlier GHSA-fqhg-287p-c6vf fix. The articles do not describe the report timeline to the vendor and do not report exploitation in the wild.
PoC pública
The Hakai Security write-up describes a working PoC validated on a self-hosted 30.2.4 instance without a restrictive script-src CSP. It uses two cells: one html=1 cell with a benign label, and one html=0;editable=0 cell carrying the payload. Opening the file is enough, and the demonstration exfiltrated cookies that were not HttpOnly, localStorage contents and the open diagram. No separate repository is mentioned. Since the technique is public, patching to 30.2.5 is urgent, especially where files arrive from untrusted sources.
Publicada em: yokai.hakaisecurity.io/en-drawing-the-vulnerability-xss-in-draw-io-cve-2026-58504/ ↗
Explicação gerada por IA a partir da matéria e dos artigos, em 27/10/2026 19:17.
Detalhes técnicos
- The sink is
mxUtils.canConvertHtmlToSvg()inmxUtils.js, which setsdiv.innerHTML = valuewithouthtmlEntitiesorsanitizeHtml. It is called fromTextFormatPanel.addFont()inFormat.js. - Root cause: three individually reasonable design decisions that become inconsistent together (a confused deputy scenario).
getLabelValuereturns plain-text (html=0) labels raw.- The
formattedgate is computed from an aggregated style that skips cells witheditable=0. - The feature-detection loop in
addFont()iterates over all selected cells, includingeditable=0ones.
- Exploit: cell A has
html=1and a benign label, which keepsformattedtrue. Cell B hashtml=0;editable=0and an<img src=x onerror=...>payload, which reachesinnerHTMLunsanitized. - Imported cells are auto-selected and
TextFormatPanelis rebuilt, so opening the file is enough to trigger the XSS with no extra interaction. - Validated on a self-hosted 30.2.4 instance without a restrictive
script-srcCSP. The payload exfiltrateddocument.cookie(for example a non-HttpOnlyrl_session), PostHog and RudderStack identifiers,localStoragecontents and the open diagram. - Impact: session hijacking, authenticated same-origin requests, and theft of OAuth or access tokens stored client-side. Delivery is through file sharing (email, Slack, tickets, pull requests).
Remediação
- Upgrade draw.io / diagrams.net to 30.2.5 or later, including embedded and self-hosted deployments.
- Enforce a restrictive CSP
script-src. - Set the HttpOnly attribute on session cookies.
- Treat
.drawiofiles from untrusted sources with caution.
Linha do tempo
-
Stored XSS in draw.io 30.2.4 (CVE-2026-58504) via malicious .drawio file, bypassing the GHSA-fqhg-287p-c6vf fix
ler a análise desta fonte ocultar
The Yokai Team at Hakai Security disclosed CVE-2026-58504 (GHSA-c76x-r78m-phwx, CWE-79), a Stored XSS in draw.io / diagrams.net 30.2.4. It triggers when a victim opens or imports a malicious
.drawiofile, and the JavaScript runs on the origin of the draw.io instance. In embedded deployments (Confluence, Jira, wikis) that means the origin of the host application. The flaw bypasses the earlier fix for GHSA-fqhg-287p-c6vf (29.7.12) and was fixed in 30.2.5. The article describes a working PoC and session and data theft on a self-hosted instance. It does not report exploitation in the wild.Technical Details
- The sink is
mxUtils.canConvertHtmlToSvg()inmxUtils.js, which setsdiv.innerHTML = valuewithouthtmlEntitiesorsanitizeHtml. It is called fromTextFormatPanel.addFont()inFormat.js. - Root cause: three individually reasonable design decisions that become inconsistent together (a confused deputy scenario).
getLabelValuereturns plain-text (html=0) labels raw.- The
formattedgate is computed from an aggregated style that skips cells witheditable=0. - The feature-detection loop in
addFont()iterates over all selected cells, includingeditable=0ones.
- Exploit: cell A has
html=1and a benign label, which keepsformattedtrue. Cell B hashtml=0;editable=0and an<img src=x onerror=...>payload, which reachesinnerHTMLunsanitized. - Imported cells are auto-selected and
TextFormatPanelis rebuilt, so opening the file is enough to trigger the XSS with no extra interaction. - Validated on a self-hosted 30.2.4 instance without a restrictive
script-srcCSP. The payload exfiltrateddocument.cookie(for example a non-HttpOnlyrl_session), PostHog and RudderStack identifiers,localStoragecontents and the open diagram. - Impact: session hijacking, authenticated same-origin requests, and theft of OAuth or access tokens stored client-side. Delivery is through file sharing (email, Slack, tickets, pull requests).
Remediation
- Upgrade draw.io / diagrams.net to 30.2.5 or later, including embedded and self-hosted deployments.
- Enforce a restrictive CSP
script-src. - Set the HttpOnly attribute on session cookies.
- Treat
.drawiofiles from untrusted sources with caution.
- The sink is