PocketBase OAuth2 Account Pre-Hijacking (CVE-2026-44166)
by Alardiians · 2026-07-05
- Severity
- Medium
- CVE
- CVE-2026-44166 / [GHSA-pq7p-mc74-g65w](https://github.com/pocketbase/pocketbase/security/advisories/GHSA-pq7p-mc74-g65w)
- Category
- web
- Affected product
- [PocketBase](https://github.com/pocketbase/pocketbase) (Go backend / BaaS)
- Affected versions
- < 0.22.42 and >= 0.30.0, < 0.37.4
- Disclosed
- 2026-07-05
- Patch status
- unpatched
Tags
References
Archive entry
intelseclab/poc-archiveMetadata
| Field | Value |
|---|---|
| Date Added | 2026-07-05 |
| Last Updated | 2026-06 |
| Author / Researcher | Alardiians |
| CVE / Advisory | CVE-2026-44166 / GHSA-pq7p-mc74-g65w |
| Category | web |
| Severity | Medium |
| CVSS Score | 6.1 (CVSS 4.0) |
| Status | PoC |
| Tags | pocketbase, oauth2, account-takeover, account-pre-hijacking, cwe-287, improper-authentication, go |
| Related | CVE-2024-38351 (incomplete prior fix for the same class of bug) |
Affected Target
| Field | Value |
|---|---|
| Software / System | PocketBase (Go backend / BaaS) |
| Versions Affected | < 0.22.42 and >= 0.30.0, < 0.37.4 |
| Language / Platform | Go web backend, exploited over its REST API |
| Authentication Required | No admin auth needed — attacker only needs their own account on a configured OAuth2 provider |
| Network Access Required | Yes — HTTP access to the target’s auth-with-oauth2 endpoint |
Summary
PocketBase’s auth-with-oauth2 endpoint accepts a client-supplied createData object when provisioning a new user record during OAuth2 sign-up, but never validates the email field inside it against the email address actually verified by the OAuth2 provider. An attacker who authenticates with their own account on any configured provider (e.g. GitHub, Google, a custom OIDC provider) can set createData.email to a victim’s email address, creating a PocketBase record that carries the victim’s identity but is linked to the attacker’s OAuth2 login. Depending on how many providers are configured, this either permanently locks the real owner out (single provider) or lets the attacker silently co-own the account indefinitely (two or more providers). This is a more complete bypass of the same account-pre-hijacking class that CVE-2024-38351 tried, and failed, to fully close.
Vulnerability Details
Root Cause
In apis/record_auth_with_oauth2.go, the oauth2Submit() new-record branch clones the client-supplied createData and only falls back to the OAuth2-provider-verified email if the client didn’t supply one:
| |
If the attacker supplies createData: {"email": "victim@company.com"}, that value wins. The new record is created unverified (since the record email doesn’t match the OAuth2-verified email) but is linked to the attacker’s {provider, providerId} via the _externalAuths table. The prior fix for CVE-2024-38351 only rotates the record’s password when a pre-registered record is later claimed — it never removes the attacker’s stale _externalAuths link, so the OAuth2 login path survives even when password auth is neutralized.
Attack Vector
- Attacker identifies a PocketBase deployment with an auth collection that has OAuth2 sign-up enabled and open registrations (the default setup for “Sign in with GitHub/Google”).
- Attacker obtains a valid OAuth2
codefor their own account on one of the configured providers. - Attacker calls
POST /api/collections/<name>/auth-with-oauth2with their owncodebutcreateData: {"email": "<victim>@<target-domain>"}. - PocketBase creates a new record with the victim’s email, linked to the attacker’s
{provider, providerId}in_externalAuths. - Single provider: when the real victim later authenticates via the same provider, a unique-index collision on
_externalAuthscauses their request to fail with a generic400 Failed to authenticate.— permanent lockout, no self-service recovery. - Two or more providers: the victim authenticates through a different provider than the attacker used; their link installs cleanly and the record becomes verified, but the attacker’s link is never removed, so both parties retain independent working logins to the same record indefinitely.
Impact
Account takeover / persistent unauthorized access to a victim’s account without any interaction from the victim (lockout variant), or silent, durable co-ownership of the victim’s account (multi-provider variant). No admin credentials or victim interaction required — only a throwaway account on one of the app’s configured OAuth2 providers.
Environment / Lab Setup
The included lab is fully offline: fetch_binaries.py downloads the vulnerable (0.37.3)
and patched (0.37.4) official PocketBase binaries; mock_oauth2_server.py stands in for
a real IdP (issues tokens/userinfo for a fixed "attacker" and "victim" identity). No
real GitHub/Google OAuth app or network target is needed.
python fetch_binaries.py # pulls vulnerable 0.37.3 + patched 0.37.4 for your OS
python poc.py # pre-claim and lockout against the vulnerable build
python verify_fix.py 0.37.4 # same steps on the patched build, attacker gets evicted
Proof of Concept
PoC Script
See
poc.py,mock_oauth2_server.py,fetch_binaries.py,verify_fix.py, andWRITEUP.mdin this folder.
| |
Running poc.py against vulnerable 0.37.3 shows the attacker pre-claiming victim@company.com, the victim’s real login failing with HTTP 400 -- Failed to authenticate., and the attacker still able to re-authenticate into the same record. Running verify_fix.py against 0.37.4 shows the victim’s login succeeding and evicting the attacker’s stale link.
Detection & Indicators of Compromise
No distinctive default log entry is produced by this attack — detection must
happen at the data layer, not the log layer.
Signs of compromise:
- Records in
_externalAuthswhose linked provider identity’s email does not match the owning record’semailfield. - Unverified records with a populated, business-relevant (e.g. corporate-domain) email that were created via an OAuth2 sign-up flow.
- A single record carrying multiple provider links that the application’s UX shouldn’t produce (e.g. both
googleandoidclinks on one record). - Spikes in
400 Failed to authenticate.responses on OAuth2 callback endpoints, which can indicate victims hitting the lockout variant.
Remediation
| Action | Detail |
|---|---|
| Primary fix | Upgrade PocketBase to 0.37.4 (0.30.x line) or 0.22.42 (LTS line), which deletes any stale _externalAuths links on a record before linking the legitimate OAuth2 owner. |
| Interim mitigation | If upgrading isn’t immediately possible, patch/validate that createData.email (when supplied) must match OAuth2User.Email from the provider, rejecting the request otherwise. |
References
Notes
Mirrored from https://github.com/Alardiians/pocketbase-CVE-2026-44166 on 2026-07-05.
| |