
Element Web v1.12.29 - High - Remote - Session - fragment guest creds wipe a real session before whoami
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · element-fragment-guest
Element Web v1.12.29 - Element / New Vector
Class: Session destruction Reach: Remote
Lifecycle.loadSession prefers guest_user_id and guest_access_token from the URL hash over restoreSessionFromStorage. Guests are on by default (enableGuest={!config.disable_guests}). app.element.io config.json does not set disable_guests. onTokenLoginCompleted strips SSO and OIDC params only. It never strips the guest keys. url_utils.ts already comments that these fragment params look unused. They still win.
Anyone who can get a signed-in user to open a hash URL with guest fragment params logs them out and wipes local crypto. A garbage token is enough. whoami is never called.
| ID | no CVE yet |
| CWE | CWE-384 / CWE-352 / CWE-613 |
| CVSS | High: 7.1 CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H |
| Product | Element Web |
| Affected | v1.12.29 tag v1.12.29 (2d90d6b7b601ecb9ccdf81239a43b4396b4b0c9e). Guests enabled (default). |
| Auth | unauthenticated attacker. Victim must open (or reload) a URL. |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
Get a signed-in user to open a hash URL with guest fragment params. The client logs them out, wipes local crypto, and may park them on an attacker guest. This is logout and crypto wipe. It is not steal-the-real-token. It is not OS RCE.
| Privilege | Method / sink | Recon / integrity / availability | Lab-proven or inferred |
|---|---|---|---|
| Parse guest creds from the hash | url_utils.ts guest: { keys: ["guest_user_id", "guest_access_token"], location: "fragment" } | Integrity of session selection | Lab-proven. #/home?guest_user_id=...&guest_access_token=... became guest_params. Query-string only did not. |
| Prefer guest over a stored non-guest session | Lifecycle.loadSession guest-first branch, log Using guest access credentials | Integrity | Lab-proven. Seeded @victim:example.org was not restored. |
| Wipe localStorage, IndexedDB account, sessionStorage, crypto | doSetLoggedIn(..., clearStorageEnabled=true) then clearStorage() | Integrity / availability of the local session | Lab-proven. crypto_empty=yes, prior token gone. |
| Skip token validation | Guest path never calls /_matrix/client/v3/account/whoami | Integrity | Lab-proven. whoami_called=no. Garbage token not-a-real-token still wiped. |
| Persist attacker guest creds | persistCredentials after the wipe | Integrity | Lab-proven. after_user=@x:example.org, after_token=not-a-real-token, mx_is_guest=true. |
| Fixate onto a real guest on the default HS | Same persist if the guest token is valid | Integrity | Inferred. Lab used a garbage token, so no live HS login. |
| Keep doing this on reload | Guest keys stay in the hash; onTokenLoginCompleted does not delete them | Integrity | Inferred from pin app.tsx. Lab opened the URL once. |
Closed on this leftover: steal the real access token (it is deleted, not exfiltrated). OS RCE / LPE (Desktop nodeIntegration: false, sandbox). Live Element SPA (how=gadget). Query-string ?guest_user_id= (fragment only). disable_guests: true keeps the non-guest session. A patched loadSession that ignores guest when a non-guest session exists also keeps it.
Same pin leftovers: vector: Windows drive-letter, Univention ICS javascript: href.
I pinned Element Web v1.12.29 after the 22 Sep 2026 GitHub release. SSO loginToken is query-only and stripped after use. Guest keys still sit in the fragment and win over restore. The comment in url_utils.ts is the tell: they already doubted these keys were specified.
Wrong turns already recorded: treating query-string ?guest_user_id= as the vector (keys live in the hash); calling this token theft (the real token is deleted, not sent); expecting disable_guests on app.element.io config (it is unset). A reverse shell. Theatre.
Gadget of the pin functions. Not a live Element Web container. how=gadget.
python3 element-fragment-guest-Abraxas-Labs.py
or
cd lab
./run.sh
Target only http://127.0.0.1:19600. Compose project element-fragment-guest. Stack tears down after the run.
SUCCESS element-fragment-guest how=gadget guest-wipe=yes prior-kept=no garbage-token=yes disable-guests-keeps=yes ELEMENT-FRAGMENT-GUEST-WITNESS
Ignore guest fragment params when a non-guest session already exists. Validate the guest token with /_matrix/client/v3/account/whoami before clearStorage. Strip guest_user_id and guest_access_token the same way SSO tokens are stripped. Default disable_guests: true for hosted app.element.io.
Lifecycle.ts (loadSession guest-first)url_utils.tsapp.tsx (enableGuest={!config.disable_guests})GNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.