# Gitea: Improper authorization on OAuth sign-in callback silently re-enables administrator-disabled accounts

Gitea's OAuth2 callback flips IsActive=true on locally-disabled accounts, letting any IdP-linked user silently undo an admin's Disable Account and regain a session.

- Payout: Research
- Severity: high
- Program: Gitea
- Found by: chndlrx
- Disclosed: 2026-07-21
- CVE: CVE-2026-58422
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-g9g6-qhrc-p3qc
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-g9g6-qhrc-p3qc
- Tags: area:web domain:web impact:ato impact:privesc kind:research platform:ghsa sev:high tech:go tech:oauth technique:source-review vuln:access-control vuln:auth-bypass

**Target:** /user/oauth2/{source-name}/callback OAuth2 sign-in handler
**Root cause:** handleOAuth2SignIn treats the local IsActive=false flag as stale bookkeeping to reconcile with the external identity, setting opts.IsActive=true and calling UpdateUser instead of rejecting with IsErrUserInactive like the local-credential path.
**Steps:**
1. Admin adds an OAuth2 auth source (Keycloak, GitHub, GitLab, etc.) via Site Administration -> Authentication Sources.
2. User alice signs in through that source once, creating an external_login_user link.
3. Admin disables alice via Site Administration -> User Accounts -> Disable Account; confirm with `GET /api/v1/admin/users/alice` returning `"active":false`.
4. Alice clicks 'Sign in with <source-name>' and completes the OAuth dance; callback observes IsActive=false, sets opts.IsActive=true, calls UpdateUser, and issues a session.
5. Re-read `GET /api/v1/admin/users/alice` and observe `"active":true` without admin action.
**How found:** Code reading of routers/web/auth/oauth.go::handleOAuth2SignIn lines 350-352, noticing the callback flips IsActive while the local-credential path renders IsErrUserInactive, and that verifyAuthWithOptions only checks the flag on subsequent requests.
**Exploitation:** A disabled user with any linked external IdP simply completes one OAuth callback to self-reactivate and receive a fresh authenticated session, regaining read/write access to repositories, organizations and tokens.
**Escalation:** Defeats the documented incident-response disable toggle in SSO-fronted deployments, allowing a compromised or departed account to persist until the IdP revokes the federated identity.
**Insight:** Compare parallel auth paths: when one login flow rejects an inactive user and another silently repairs the flag, the 'repair' branch is an authorization bypass — check every place a session is minted for state-mutating side effects on the user row.

## Proof of concept (copied exactly from the original report)

**Admin API read showing alice active:false**

```
GET /api/v1/admin/users/alice HTTP/1.1
Host: <HOST>
Authorization: token <ADMIN_TOKEN>
Accept: application/json

#### HTTP RESPONSE

HTTP/1.1 200 OK
Content-Type: application/json;charset=utf-8

{"id":2,"login":"alice","active":false,...}
```

