# Open WebUI: Account takeover via OAuth token exchange accepting tokens issued to any client

Open WebUI's OAuth token exchange validated tokens only via userinfo, so a token minted for any unrelated client on the same provider yielded a full session as that user.

- Payout: Research
- Severity: high
- Program: Open WebUI
- Found by: Classic298
- Disclosed: 2026-08-04
- CVE: CVE-2026-70482
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-rq84-p6rr-vf89
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-rq84-p6rr-vf89
- Tags: area:web domain:web impact:ato impact:privesc kind:research platform:ghsa sev:high tech:oauth tech:oidc technique:source-review vuln:access-control vuln:account-takeover vuln:auth-bypass

**Target:** OAuth token exchange endpoint POST /api/v1/auths/oauth/{provider}/token/exchange
**Root cause:** Token exchange skipped the authorization-code step and validated the caller-supplied access token only by calling the provider's userinfo endpoint, which reports validity but not the issuing client; no audience or client_id check was performed.
**Steps:**
1. Enable `ENABLE_OAUTH_TOKEN_EXCHANGE=True` on the target (non-default).
2. Register a self-service OAuth app (`attacker-evil-app`) on the same provider and get the victim to sign in to it, obtaining an access token for the victim.
3. Confirm the provider issues a subject stable across clients (Google, GitHub, Okta, default OIDC); Entra ID per-app subjects fail unless `OAUTH_MERGE_ACCOUNTS_BY_EMAIL` is set or `OAUTH_SUB_CLAIM=oid`.
4. POST the victim's foreign-client token to `/api/v1/auths/oauth/{provider}/token/exchange`.
5. Receive a 200 with an Open WebUI session for the victim; verify with `GET /api/v1/auths/` returning 200 authenticated as the victim.
**How found:** Noticed the exchange path bypasses the authorization-code flow and relies solely on a userinfo call; reasoned that userinfo answers 'is this token valid' but not 'which client was it issued to', making any same-provider token interchangeable. Validated with a mock OIDC provider issuing two tokens for one user under two client_ids, with the domain allowlist enforced.
**Exploitation:** Attacker registers an unrelated OAuth application on the same identity provider, lures the victim through a normal consent flow to obtain an access token minted for that app, then submits it to Open WebUI's token exchange endpoint. The userinfo lookup succeeds, the subject matches the victim's stored account, and a session is issued carrying the victim's role — administrator if the victim is one. Victim never touches Open WebUI.
**Escalation:** No role gating on the endpoint: targeting an administrator yields an administrator session. `OAUTH_ALLOWED_DOMAINS` does not help since the impersonated user is in an allowed domain.
**Insight:** Any endpoint that accepts a bearer token and validates it via userinfo/introspection-lite is client-agnostic: check whether the provider exposes RFC 7662 introspection with client_id, and test with a token you minted for your own self-registered app on the same IdP. Also note `aud` is the wrong field — it names resource servers, not the issuing client.

