
Element Web v1.12.29 - High - Remote - XSS - Univention ICS navigation.json link becomes a javascript: menu href
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · element-ics-javascript
Element Web v1.12.29 - Element / New Vector
Class: XSS Reach: Remote
The Univention banner module fetches ICS navigation.json, accepts each entry's link as z.string() with no scheme check, and Menu.tsx puts that string on a styled <a href>. Static menu config uses z.url() for link_url. The ICS path does not. URL preview on the same pin already rejects javascript: (previewFromBundle, GHSA-9r5h). This is not default app.element.io. It needs modules/banner with menu.type univention, plus an ICS the deploy trusts.
Anyone who can change that navigation.json plants javascript: or data:. A signed-in user who opens the banner menu and clicks the item runs script on the Element origin.
| ID | no CVE yet |
| CWE | CWE-79 / CWE-20 |
| CVSS | Medium: 6.8 CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N |
| Product | Element Web |
| Affected | v1.12.29 tag v1.12.29 (2d90d6b7b601ecb9ccdf81239a43b4396b4b0c9e). Gated: Univention banner + ICS. |
| Auth | ICS operator / ICS compromise. Victim is a signed-in user who clicks the menu item. |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
Compromise or operate the ICS that the Element deploy trusts. Put a javascript: (or data:) URL in navigation.json. Wait for a signed-in user to open the banner menu and click the item. That is Matrix account takeover: access token, crypto store, the session. It is not OS RCE. Desktop still has nodeIntegration off and a sandbox.
| Privilege | Method / sink | Recon / integrity / availability | Lab-proven or inferred |
|---|---|---|---|
Render a banner-menu <a href="javascript:…"> on the Element origin | ICS navigation.json entries[].link -> navigationToConfig link_url -> Menu.tsx LinkButton href={link.link_url} | Integrity of the menu DOM | Lab-proven. Mock ICS link javascript:ELEMENT-ICS-JAVASCRIPT-WITNESS became that href. https portal link still rendered |
| Run script as the Element origin after a click | Victim clicks the menu item (UI:R) | Integrity | Inferred from the href sink. Lab did not drive a browser click or steal a live Matrix token |
| Read the Matrix access token / crypto store (account takeover) | In-origin script vs localStorage / IndexedDB session | Recon / integrity | Inferred from Element origin XSS. Lab witness is the href string |
| Top-navigate the Element tab to a phishing page | SilentLogin.tsx unsandboxed iframe src={new URL("silent", icsUrl)} | Integrity | Lab-proven the iframe has no sandbox. Top-navigation not exercised |
Same plant via data: | z.string() accepts any scheme | Integrity | Inferred. Lab planted javascript: only |
Closed on this pin: default app.element.io (no Univention banner). OS RCE / LPE (Desktop nodeIntegration: false, sandbox, contextIsolation). Timeline HTML XSS. URL-preview javascript: (GHSA-9r5h previewFromBundle requires http:/https:). HTML export XSS (GHSA-wqmv). Widget media token (GHSA-3jm3). Static banner link_url: z.url() is operator config, not ICS; z.url() is also not an http/https allowlist.
Same pin leftovers: fragment guest wipe, vector: Windows drive-letter.
I pinned Element Web v1.12.29 after the 22 Sep 2026 GitHub release. URL preview already learned http:/https: the hard way. Univention ICS link is still z.string(). Static menu config uses z.url(). The fetch path never goes through that check.
Wrong turns already recorded: treating default app.element.io as the victim (Univention is gated); treating z.url() as a scheme allowlist (the URL constructor accepts javascript:); inflating click XSS to OS RCE. A reverse shell. Theatre.
Gadget of the pin functions plus a mock ICS navigation.json. Not a live Element Web container. how=gadget. Applying the URL-preview http/https allowlist (previewFromBundle) dropped the javascript href and kept the https portal link.
python3 element-ics-javascript-Abraxas-Labs.py
or
cd lab
./run.sh
Target only http://127.0.0.1:19620. Compose project element-ics-javascript. Stack tears down after the run.
SUCCESS element-ics-javascript how=gadget href-scheme=javascript menu-a-javascript=yes allowlist-rejects=yes silent-no-sandbox=yes ELEMENT-ICS-JAVASCRIPT-WITNESS
Allowlist http: and https: on ICS link the same way previewFromBundle does. Reject javascript:, data:, and other schemes before href=. Sandbox the silent iframe (sandbox="" or at least no allow-top-navigation). Do not treat z.url() as a scheme allowlist.
navigation.tsMenu.tsxSilentLogin.tsxpreviewFromBundleGNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.