
Proof-of-concept and lab for a Gitea IDOR (CWE-639) in GET /api/v1/user/keys/{id}, letting an authenticated read:user token enumerate other users' SSH and deploy keys.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitea-user-keys-idor
Gitea 1.27.3 - Gitea
GET /api/v1/user/keys/{id} sits in the current-user group (reqToken, rejectPublicOnly). The handler still loads the row by numeric ID with no owner check. The same table holds user keys, deploy keys, and principals. GPG's sibling does it right: GetGPGKeyForUserByID(ctx, ctx.Doer.ID, id). Private fields are gated. The public key material, title, and fingerprint are not. IDs are sequential.
A read:user token walks 1, 2, 3 and inventories other people's SSH and deploy keys. Open registration makes that a new-account bug.
| ID | no CVE yet |
| CWE | CWE-639 |
| CVSS | High: 7.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| Product | Gitea |
| Affected | through v1.27.3 (146cc3e) |
| Auth | authenticated (read:user token) |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
Register (default open registration), mint a read:user token, walk /api/v1/user/keys/{id}. They get titles, fingerprints, and public key material for keys they do not own, including deploy keys. They do not get the private key. They do not get a shell. They inventory who has access to what.
The 1.27.3 key GHSA was username-scoped list plus individualPermsChecker. It was not this lookup.
Same tag leftover: git HTTP redirect SSRF.
v1.27.3 closed the 2026 GHSA wave. I sat on that tag anyway and read the current-user key handler next to GPG. GPG 404s a foreign id. SSH does not.
seed.sh plants victim key title GHSA-GITEA-KEY-IDOR-WITNESS and an attacker key. Attacker's own list does not contain the witness. GET /user/keys/1 as the attacker does. GPG with a foreign id 404s. That is the whole argument.
Wrong turns already recorded: treating the attacker's own list as the leak (it is not - list is scoped, GET-by-id is not); a reverse shell. Theatre.
Client bugs look like product bugs. A helper that shadows http.client never sends a packet. .recv() on an HTTPResponse is not .read(). I mention that once because it cost time and looked, for a minute, like 1.27.3 had finished the job.
cd lab
./run.sh
Target only http://127.0.0.1:18101.
add-key user=victim status=201 title=GHSA-GITEA-KEY-IDOR-WITNESS id=1
add-key user=attacker status=201 id=2
attacker-list status=200 ids=[2] (no witness)
get id=1 status=200 snippet contains GHSA-GITEA-KEY-IDOR-WITNESS
SUCCESS Gitea #1 user/keys IDOR
404 unless key.OwnerID == ctx.Doer.ID (or site admin). Reject deploy/principal types. Match GPG.
GetPublicKeyGNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.