contents [06]
  1. A API de gerenciamento
  2. O fluxo da falha
  3. O HMAC não vinculava a operação
  4. Os mesmos bytes, outro significado
  5. PoC
  6. Timeline

Em agosto, reportei uma falha no GitHub Enterprise Server (GHES) que começava numa checagem de hardware acessível sem autenticação. Essa checagem fazia chamadas internas aos nós do cluster. Consegui redirecionar uma delas e fazer o GHES devolver uma credencial da API de gerenciamento como mensagem de erro.

Com essa credencial, cadastrei uma chave SSH de admin pela API, o que me deu controle sobre o servidor.

A API de gerenciamento

O GHES é a versão do GitHub que uma organização instala na própria infraestrutura. Pela Manage API, dá para consultar configurações, acompanhar serviços e gerenciar o acesso ao GHES.

Num cluster, os serviços se distribuem entre vários nós. O gateway da Manage API recebe o pedido do cliente e consulta por RPC os agentes nessas máquinas. Para verificar o hardware, ele chama CheckSystemRequirements e reúne os resultados, incluindo possíveis mensagens de erro dos nós.

O fluxo da falha

A checagem aceitava, sem exigir autenticação, uma configuração de cluster com os endereços dos nós. O gateway enviava chamadas a CheckSystemRequirements para esses endereços. Com meu servidor nessa lista, a chamada chegava até ele com a autenticação interna usada pelo GHES para falar com os agentes.

Meu servidor respondia com um redirecionamento para GetSecrets, a operação interna do GHES que consultava credenciais. O cliente RPC seguia o redirecionamento com a mesma autenticação, que também era aceita em GetSecrets. A resposta dessa operação incluía uma chave com acesso administrativo à Manage API.

No retorno, o gateway tratava a resposta de GetSecrets como se viesse de CheckSystemRequirements. A chave era lida como uma mensagem de erro de um nó e devolvida a quem havia feito a requisição inicial. Na figura, A representa CheckSystemRequirements e B, GetSecrets.

A redirect changes the RPC operation from A to B. The gateway retains its ReplyA decoder, accepts a compatible field from response bB, and returns the credential as an error.
The RPC operation changes while the response decoder remains bound to the original call.

O HMAC não vinculava a operação

O gateway autenticava as chamadas aos agentes com um HMAC calculado apenas sobre o timestamp. Como o cálculo não incluía a operação nem o corpo da requisição, redirecionar a chamada para outra operação não invalidava a autenticação.

Conceptual HMAC input. Key K and timestamp t produce the tag. The RPC path and Protobuf body remain outside the authenticated data.
The MAC authenticates the timestamp without binding the operation or request body.

Os mesmos bytes, outro significado

As mensagens internas usavam Protobuf. Na representação binária, cada campo leva um número e um wire type, que indica como ler o valor. Para strings, por exemplo, o decoder lê primeiro o tamanho e depois os bytes do texto.

Os bytes não incluem os nomes dos campos ou da mensagem, então o decoder depende do schema para interpretá-los. No exemplo fictício da figura, o campo 7 é uma string tanto em ToyA.text quanto em ToyB.label. Assim, os mesmos bytes de ready podem preencher qualquer um dos dois.

Illustrative schemas. Tag 0x3A encodes field 7 and wire type 2. Byte 0x05 gives the payload length. ToyA.text and ToyB.label both accept the same five UTF-8 bytes for ready.
Illustrative schemas. The tag carries the field number and wire type, not the field name.

No GHES, a resposta de GetSecrets guardava a chave num campo com o mesmo número e tipo do campo de erro da resposta de CheckSystemRequirements. Ambos aceitavam strings, então o decoder lia a chave como uma mensagem de erro.

A compatibilidade entre os schemas explicava por que o decoder aceitava a resposta da operação errada sem falhar.

PoC

Criei um script de PoC para automatizar todo o processo, incluindo o envio da configuração de cluster, o SSRF via redirect interno, a extração da chave da Manage API vazada no campo de erro do Protobuf e, por fim, o cadastro de uma chave SSH para admin sob meu controle. Reproduzi essa cadeia no GHES 3.21.3, 3.21.4 e 3.21.5.

Gravação original do PoC executando contra meu servidor GHES local.

Timeline

Data Evento
02/08/2026 Enviei o report ao GitHub.
14/08/2026 O GitHub encerrou o report como duplicado de um caso anterior.
01/09/2026 Publicação da CVE-2026-18730, associada ao report original.
26/09/2026 Revalidei na 3.21.6 e confirmei que a cadeia já não reproduzia o resultado anterior.