
Forgejo 16.0.5 – Hoch – Remote – IDOR – GET /api/v1/user/keys/{id} gibt die SSH- und Deploy-Schlüssel anderer Benutzer zurück
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · forgejo-keys-idor
Write-up: https://abraxaslabs.tech/research/forgejo-keys-idor
Klasse: IDOR Reichweite: Remote
Forgejo 16.0.5 (8d24b7d) - Forgejo
GET /api/v1/user/keys/{id} liegt in der Gruppe für den aktuellen Benutzer (reqToken, Benutzer-Scope). Der Handler lädt public_key dennoch anhand der numerischen ID, ohne Eigentümerprüfung. Dieselbe Tabelle enthält Benutzerschlüssel, Deploy-Keys und Principals. GPG macht es in diesem Stand richtig: GetGPGKeyForUserByID(ctx, ctx.Doer().ID, id). Private Zusatzfelder sind geschützt. Das öffentliche Schlüsselmaterial, der Titel und der Fingerprint sind es nicht. IDs sind fortlaufend.
Ein read:user-Token läuft 1, 2, 3 durch und inventarisiert die SSH- und Deploy-Keys anderer Personen.
| ID | noch keine CVE |
| Klasse | IDOR |
| Reichweite | Remote (authentifiziertes read:user / Session) |
| CWE | CWE-639 |
| CVSS | Hoch: 7.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| Produkt | Forgejo |
| Betroffen | bis v16.0.5 (8d24b7d) |
| Auth | authentifiziert (read:user-Token oder Session) |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Ein beliebiges Konto registrieren oder verwenden, ein read:user-Token erzeugen (oder ein Session-Cookie nutzen) und /api/v1/user/keys/{id} durchlaufen. Man erhält Titel, Fingerprints und öffentliches Schlüsselmaterial für Schlüssel, die einem nicht gehören. Den privaten Schlüssel erhält man nicht. Eine Shell erhält man nicht. Man inventarisiert, wer Zugriff auf was hat.
| Berechtigung | Methode / Sink | Recon / Integrität / Verfügbarkeit | Im Lab nachgewiesen oder abgeleitet |
|---|---|---|---|
| Titel, Fingerprint und Schlüsselmaterial des SSH-Public-Keys eines anderen Benutzers lesen | GET /api/v1/user/keys/{id} als fremder Eigentümer | Recon (Vertraulichkeit des Public-Key-Inventars) | Im Lab nachgewiesen. Witness-Titel FORGEJO-KEYS-IDOR-WITNESS auf ID 1; Angreiferliste enthält nur ID 2 |
| Deploy-Keys auf dieselbe Weise inventarisieren | Derselbe Handler, KeyTypeDeploy-Zeilen in public_key (OwnerID 0) | Recon | Aus der Tabelle abgeleitet. Im Lab wurde ein Benutzer-SSH-Key platziert, kein Deploy-Key |
| SSH-Zertifikats-Principals lesen | Derselbe Handler, KeyTypePrincipal-Zeilen. ListPublicKeys schließt diesen Typ aus | Recon | Aus der Tabelle abgeleitet |
| Schlüssel sehen, die eingeschränkten oder privaten Benutzern gehören | Keine Sichtbarkeitsprüfung bei diesem Lookup | Recon | Aus dem Handler abgeleitet |
Zusätzliches JSON (user, key_type, read_only) | appendPrivateInformation nur bei Admin oder Eigentümer | Recon | Im Lab nachgewiesen geschlossen: Antwort für Nicht-Eigentümer enthielt kein user-Feld |
Geschlossen auf dieser Route: privates Schlüsselmaterial, eine Shell, GET /users/{username}/keys (benutzernamenbezogene öffentliche Liste), GPG GET /api/v1/user/gpg_keys/{id} (404 bei fremder ID), DeletePublicKey (Eigentümer/Admin 403). Cross-Repo GET /repos/{owner}/{repo}/keys/{id} liefert 404, wenn RepoID nicht übereinstimmt.
Die Sicherheitspatches von v16.0.5 (#14370) waren git-apply RCE und OpenID CSRF. Sie haben dieses Lookup nicht geschlossen. Gleiche Klasse wie gitea-user-keys-idor, hier unabhängig weiterhin offen.
Ich habe Forgejo v16.0.5 nach der Welle vom 2026-09 festgelegt. Gitea 1.27.3 hatte dieses Lookup noch. Forgejo hat sich auseinanderentwickelt, also habe ich den Key-Handler für den aktuellen Benutzer erneut neben GPG gelesen, statt das Gitea-Paket zu kopieren. GPG liefert bei fremder ID 404. SSH nicht.
Falsche Wendungen, die bereits festgehalten wurden: die Liste des Angreifers selbst als Leak zu behandeln (das ist sie nicht – die Liste ist eingegrenzt, GET-by-id nicht); DISABLE_REGISTRATION=true beim Installer als Abschluss zu behandeln (jedes bestehende Konto genügt); eine Reverse Shell. Theater.
python3 forgejo-keys-idor-Abraxas-Labs.py
oder:
cd lab
./run.sh
Ziel nur http://127.0.0.1:19310. Image codeberg.org/forgejo/forgejo:16.0.5. Zwei Konten. Witness FORGEJO-KEYS-IDOR-WITNESS.
IOC planted victim-id=1 attacker-id=2
IOC attacker-list status=200 ... title=attacker-own-key
IOC get id=1 status=200 ... FORGEJO-KEYS-IDOR-WITNESS
IOC gpg-neg id=1 status=404
SUCCESS forgejo-keys-idor victim-id=1 get-http=200 list-has-id=no gpg-http=404 FORGEJO-KEYS-IDOR-WITNESS
404, es sei denn key.OwnerID == ctx.Doer().ID (oder Site-Administrator). KeyTypeDeploy / KeyTypePrincipal auf dieser Route ablehnen. Mit GetGPGKeyForUserByID abgleichen.
GetPublicKeyGNU Affero GPL v3.0. Siehe LICENSE. Nur Loopback-Lab. Keine Gewährleistung.