Gitea OAuth2 Scope Enforcement Bypass via HTTP Basic Auth — CVE-2026-28699
by Alardiians · 2026-07-05
- Severity
- High
- CVE
- CVE-2026-28699
- Category
- web
- Affected product
- Gitea (code.gitea.io/gitea)
- Affected versions
- <= 1.26.1 (fixed in 1.26.2)
- Disclosed
- 2026-07-05
- Patch status
- patched
Archive entry
intelseclab/poc-archiveMetadata
| Field | Value |
|---|---|
| Date Added | 2026-07-05 |
| Last Updated | 2026-06 |
| Author / Researcher | Alardiians |
| CVE / Advisory | CVE-2026-28699 |
| Category | web |
| Severity | High |
| CVSS Score | Not specified in source |
| Status | PoC |
| Tags | gitea, oauth2, scope-bypass, basic-auth, incorrect-authorization, cwe-863, api |
| Related | N/A |
Affected Target
| Field | Value |
|---|---|
| Software / System | Gitea (code.gitea.io/gitea) |
| Versions Affected | <= 1.26.1 (fixed in 1.26.2) |
| Language / Platform | Python PoC (requests) against a Go web application |
| Authentication Required | Yes (a valid, narrowly-scoped OAuth2 access token) |
| Network Access Required | Yes |
Summary
Gitea lets an OAuth2 application obtain an access token restricted to a subset of a user’s permissions (e.g. read:user only), and enforces that restriction through a tokenRequiresScopes middleware. The middleware relies on an ApiTokenScope value that is correctly attached to the request context when the token is presented as a Bearer header, but is never attached when the same token is presented via HTTP Basic auth (Authorization: Basic base64(<token>:x-oauth-basic)). Because the middleware treats a missing scope value as “nothing to check” and returns early, any OAuth2 token switched from Bearer to Basic silently loses all scope enforcement. The PoC provisions a disposable Gitea instance, mints a read:user-only token via the OAuth2 authorization-code flow, and demonstrates that a write request blocked over Bearer succeeds over Basic.
Vulnerability Details
Root Cause
services/auth/basic.go’s OAuth2 branch discards the token’s scope (_, uid := GetOAuthAccessTokenScopeAndUserID(...)) and never sets ApiTokenScope in the request context, while tokenRequiresScopes() fails open (skips enforcement) whenever that key is absent.
Attack Vector
- Obtain (or have a user authorize) an OAuth2 application token scoped only to
read:user. - Send the token as a Bearer header against a write endpoint — request is correctly blocked (403).
- Re-send the identical token as HTTP Basic auth (
Authorization: Basic base64(token:x-oauth-basic)) against the same write endpoint. - The scope check is skipped entirely and the write succeeds (200), allowing profile edits, email additions, and repository creation/modification/deletion beyond the token’s granted scope.
Impact
Any OAuth2 app or attacker holding a narrowly-scoped read-only token can perform arbitrary write actions as the authorizing user (profile changes, email additions enabling account takeover paths, repo creation/deletion), nullifying the OAuth2 consent boundary without escalating beyond the user’s own underlying permissions.
Environment / Lab Setup
Target: Gitea 1.26.1 (vulnerable) / 1.26.2 (patched), run via fetch_binaries.py
Attacker: Python 3 with `requests` (see requirements.txt)
Proof of Concept
PoC Script
See
poc.pyandfetch_binaries.pyin this folder.
| |
fetch_binaries.py downloads vulnerable (1.26.1) and patched (1.26.2) Gitea binaries; poc.py spins up a throwaway SQLite-backed Gitea instance, runs the OAuth2 authorization-code flow to mint a read:user-only token, then issues the same write request via Bearer and Basic auth, printing the pass/fail verdict for each.
Detection & Indicators of Compromise
Signs of compromise:
- Write operations (profile updates, email additions, repo create/delete) from OAuth2 apps that were only ever granted read scopes.
- Basic-auth requests to Gitea’s API using an OAuth2 access token instead of a password.
- Unexpected email addresses added to user accounts shortly after an OAuth2 app authorization.
Remediation
| Action | Detail |
|---|---|
| Primary fix | Upgrade to Gitea 1.26.2, which sets ApiTokenScope on the Basic-auth OAuth2 path (GHSA-9r5x-wg6m-x2rc). |
| Interim mitigation | Disable or tightly restrict OAuth2 application grants; monitor for Basic-auth API calls carrying OAuth2 tokens; consider making the scope middleware fail closed. |
References
Notes
Mirrored from https://github.com/Alardiians/gitea-CVE-2026-28699 on 2026-07-05.
| |