# Open WebUI: Sign-in as another user via wildcard characters in the OAuth subject claim on SQLite

Open WebUI on SQLite matched OAuth/SCIM subject claims with LIKE substring semantics, so wildcard-bearing subjects bound sign-in to the wrong account, potentially an admin.

- Payout: Research
- Severity: high
- Program: Open WebUI
- Found by: Classic298
- Disclosed: 2026-09-10
- CVE: CVE-2026-87016
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-wpmr-8h3q-fwj7
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-wpmr-8h3q-fwj7
- Tags: area:web domain:web impact:ato impact:privesc kind:research platform:ghsa sev:high tech:python technique:default-creds technique:source-review vuln:access-control vuln:account-takeover vuln:auth-bypass

**Target:** OAuth/OIDC subject and SCIM external-ID identity lookup (SQLAlchemy JSON column on SQLite)
**Root cause:** The `oauth`/`scim` columns use generic SQLAlchemy `JSON`, which lacks a containment comparator, so `contains()` degraded to a `LIKE '%value%'` substring test on SQLite where `%` and `_` are wildcards. The separately written PostgreSQL JSONB equality branch was correct, masking the asymmetry.
**Steps:**
1. Deploy Open WebUI on SQLite with OIDC configured; seed admin with subject `admin_sub_9999` and user `bob_sub_1234`.
2. Call `get_user_by_oauth_sub` with `%` — returns the administrator account.
3. Call with `admin_sub_%` — also returns the administrator account.
4. Call with an ordinary subject containing `_` — matches multiple accounts, returned row depends on order.
**How found:** Noticed the two dialect branches looked symmetrical but only one did what it appeared to; traced `contains()` on a generic JSON column to a LIKE-based substring comparison on SQLite.
**Exploitation:** Where the operator points `OAUTH_SUB_CLAIM` at a user-settable claim (username/email) or the IdP lets users choose their subject, an attacker registers with a value like `%` or `admin_sub_%` and the OAuth callback issues a session for the matched account, including admin. SCIM path requires the privileged SCIM bearer token.
**Escalation:** Matching an administrator row yields a full admin session, i.e. complete instance compromise; no downstream permission check mitigates since the wrong account is selected before session issuance.
**Insight:** When a codebase hand-writes per-dialect branches that look symmetric, verify each actually emits the intended operator — SQLAlchemy's generic `JSON` type silently degrades `contains()` to `LIKE` on SQLite, so any identity/authorization lookup using it is a wildcard-injection candidate.

