
Forgejo 16.0.5 - Alta - Remoto - IDOR - GET /api/v1/user/keys/{id} retorna as chaves SSH e de deploy de outros usuários
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · forgejo-keys-idor
Classe: IDOR Alcance: Remoto
Forgejo 16.0.5 (8d24b7d) - Forgejo
GET /api/v1/user/keys/{id} está no grupo do usuário atual (reqToken, escopo de usuário). O handler ainda carrega public_key por ID numérico sem verificação de proprietário. A mesma tabela contém chaves de usuário, deploy keys e principals. O GPG nesta versão faz certo: GetGPGKeyForUserByID(ctx, ctx.Doer().ID, id). Campos extras privados são protegidos. O material da chave pública, o título e a impressão digital não são. Os IDs são sequenciais.
Um token read:user percorre 1, 2, 3 e inventaria as chaves SSH e deploy keys de outras pessoas.
| ID | sem CVE ainda |
| Classe | IDOR |
| Alcance | Remoto (read:user autenticado / sessão) |
| CWE | CWE-639 |
| CVSS | Alto: 7.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| Produto | Forgejo |
| Afetado | até v16.0.5 (8d24b7d) |
| Autenticação | autenticado (token read:user ou sessão) |
| Licença | GNU Affero GPL v3.0 |
| Lab | apenas 127.0.0.1 |
Registrar ou usar qualquer conta, gerar um token read:user (ou usar um cookie de sessão), percorrer /api/v1/user/keys/{id}. Obtêm títulos, impressões digitais e material de chave pública de chaves que não lhes pertencem. Não obtêm a chave privada. Não obtêm um shell. Inventariam quem tem acesso a quê.
| Privilégio | Método / sink | Recon / integridade / disponibilidade | Comprovado em lab ou inferido |
|---|---|---|---|
| Ler o título, a impressão digital e o material da chave pública SSH de outro usuário | GET /api/v1/user/keys/{id} como proprietário estrangeiro | Recon (confidencialidade do inventário de chaves públicas) | Comprovado em lab. Título testemunha FORGEJO-KEYS-IDOR-WITNESS no id 1; a lista do atacante é apenas o id 2 |
| Inventariar deploy keys da mesma forma | Mesmo handler, linhas KeyTypeDeploy em public_key (OwnerID 0) | Recon | Inferido da tabela. O lab plantou uma chave SSH de usuário, não uma deploy key |
| Ler principals de certificado SSH | Mesmo handler, linhas KeyTypePrincipal. ListPublicKeys exclui este tipo | Recon | Inferido da tabela |
| Ver chaves que pertencem a usuários limitados ou privados | Nenhuma verificação de visibilidade nesta consulta | Recon | Inferido do handler |
JSON extra (user, key_type, read_only) | appendPrivateInformation apenas se admin ou proprietário | Recon | Comprovado em lab como fechado: a resposta de não-proprietário não tinha o campo user |
Fechado nesta rota: material de chave privada, um shell, GET /users/{username}/keys (lista pública com escopo de username), GPG GET /api/v1/user/gpg_keys/{id} (404 em um id estrangeiro), DeletePublicKey (proprietário/admin 403). Cross-repo GET /repos/{owner}/{repo}/keys/{id} retorna 404 quando RepoID não corresponde.
Os patches de segurança da v16.0.5 (#14370) eram RCE via git-apply e CSRF no OpenID. Não fecharam esta consulta. Mesma classe que gitea-user-keys-idor, independentemente ainda aberta aqui.
Fixe na Forgejo v16.0.5 após a onda de 2026-09. O Gitea 1.27.3 ainda tinha esta consulta. A Forgejo divergiu, então reli o handler de chaves do usuário atual ao lado do GPG em vez de copiar o pacote do Gitea. O GPG retorna 404 para um id estrangeiro. O SSH não.
Caminhos errados já registrados: tratar a própria lista do atacante como o vazamento (não é - a lista tem escopo, o GET-by-id não); tratar DISABLE_REGISTRATION=true no instalador como um fechamento (qualquer conta existente basta); um reverse shell. Teatro.
python3 forgejo-keys-idor-Abraxas-Labs.py
ou:
cd lab
./run.sh
Alvo apenas http://127.0.0.1:19310. Imagem codeberg.org/forgejo/forgejo:16.0.5. Duas contas. Testemunha 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 a menos que key.OwnerID == ctx.Doer().ID (ou admin do site). Rejeitar KeyTypeDeploy / KeyTypePrincipal nesta rota. Igualar a GetGPGKeyForUserByID.
GetPublicKeyGNU Affero GPL v3.0. Veja LICENSE. Apenas lab em loopback. Sem garantia.