high · 8.1CVE-2026-87016Sep 10, 2026

CVE-2026-87016: Open WebUI OAuth Subject Wildcard Authentication Bypass

Shubham Kandhare
Security Engagement Manager, SecureLayer7

On default SQLite deployments with OAuth or OIDC enabled, an attacker can sign in as any user, including an administrator, by supplying a wildcard character as the OAuth subject claim, because the…

Packageopen-webui
Ecosystempip
Affected>= 0.6.41, < 0.11.1
Fixed in0.11.1
CVE-2026-87016: Open WebUI OAuth Subject Wildcard Authentication Bypass

The problem

Open WebUI versions 0.6.41 through 0.11.0 contain a broken identity resolution in get_user_by_oauth_sub and get_user_by_scim_external_id inside backend/open_webui/models/users.py. On SQLite (the default backend), calling SQLAlchemy's contains() on a JSON-typed column falls back to a generic string operator that compiles to a SQL LIKE with the operand wrapped in % on both sides.

This means the subject value is matched as a substring against the serialized JSON, not as an exact value. SQL wildcard characters (% matches any sequence, _ matches any single character) in the supplied subject therefore match accounts they were never issued for.

The sign-in binds to whichever account the database returns first, which can be an administrator. PostgreSQL deployments use a separate JSONB equality path and are not affected.

Proof of concept

A working proof-of-concept for CVE-2026-87016 in open-webui, with the exact payload below.

python
# OIDC callback: attacker supplies this as the subject claim value
# Resolves to the first account in the DB (often the admin)
sub = "%"

# Or, to target a specific admin account whose subject starts with "admin_sub_":
sub = "admin_sub_%"

# Internally, the vulnerable code builds a query equivalent to:
# SELECT * FROM user WHERE oauth_sub LIKE '%' || json_value || '%'
# So sub="%" matches every row; sub="admin_sub_%" matches any row whose
# serialized JSON contains "admin_sub_" followed by anything.

SQLAlchemy's generic JSON type has no containment comparator, so column.contains(value) falls through to the string-level operator and emits column LIKE '%' || value || '%' on SQLite. The intent was a JSON containment test; what executed was a substring LIKE against the serialized column text, where % and _ carry their standard SQL wildcard meaning.

The PostgreSQL branch was written separately against JSONB using an equality comparison, which is correct. The two branches looked symmetrical in code review, hiding the defect on the default backend. The patch (PR #28624, commit 73c1f580) replaces both contains() calls with SQLAlchemy's JSON subscript operator (column[key] == value), which emits a proper equality predicate on both SQLite and PostgreSQL and removes the hand-written per-dialect branching entirely.

The fix

Upgrade to open-webui 0.11.1. The fix is in PR #28624 (commit 73c1f5806aeb6345dad5de8f5aa26d1f3d0bef80): both get_user_by_oauth_sub and get_user_by_scim_external_id now use SQLAlchemy's JSON subscript operator for exact-match comparison instead of contains().

No configuration change is required. Existing stored identities are unaffected. If you cannot upgrade immediately, disabling OAuth/OIDC and SCIM (all are off by default) removes the attack surface entirely.

Reported by Classic298.

References: [1][2][3][4][5][6]

Related research