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

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
gortex-PoC — PoC — Gortex의 search_text를 통한 저장소 외부 콘텐츠 노출로 이어지는 심볼릭 링크 추적 (GHSA-6vhf-4wcm-2r83, CVE-2026-87003, CVSS 5.5). | Kitploit
도구/GitHubGitHub/squeeze440/gortex-poc
Static AnalysisVulnerability AnalysisCode AnalysisExploitationInformation GatheringWeb SecurityPapers & Research
GitHubsqueeze440/gortex-poc

gortex-PoC

PoC — Gortex의 search_text를 통한 저장소 외부 콘텐츠 노출로 이어지는 심볼릭 링크 추적 (GHSA-6vhf-4wcm-2r83, CVE-2026-87003, CVSS 5.5).

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

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

인덱서 파일 순회에서의 심볼릭 링크 추적 → search_text를 통한 저장소 외부 콘텐츠 노출

CVE 상태: 요청됨, 할당 대기 중. 이 발견은 GHSA-6vhf-4wcm-2r83으로 공개되었습니다. CVE가 할당되면 이 저장소는 CVE-YYYY-NNNNN-gortex-PoC로 이름이 변경되고 이 배너는 CVE 링크로 대체됩니다.

연구자Dostxodjayev Abdullox (@squeeze440)
권고GHSA-6vhf-4wcm-2r83
CVSS 3.15.5 (Medium)
취약점CWE-59, CWE-200

인덱서 파일 순회에서의 심볼릭 링크 추적 → search_text를 통한 저장소 외부 콘텐츠 노출

요약

gortex의 저장소 인덱서에 있는 부적절한 링크 해석 결함(CWE-59)으로 인해, 인덱싱된 저장소 루트 외부를 가리키는 심볼릭 링크된 파일 항목이 조용히 순회되고, 콘텐츠가 인덱싱되며, 이후 search_text MCP 도구의 실시간 재읽기 싱크를 통해 그대로 제공될 수 있습니다 — 완전히 루트 외부에 있는 대상에게도 마찬가지입니다. 이 싱크는 프로젝트 자체의 guardSymlinkWithinRepo 격리 검사를 적용하지 않습니다.

제품

zzet/gortex — https://github.com/zzet/gortex

테스트된 버전

커밋 b5a63e25c0719c8ce935e59abf85618e2b4b1e64 (git describe: v0.62.0-35-gb5a63e25), 감사 시점의 저장소 HEAD. 최신 태그 릴리스 v0.62.0에서도 여전히 존재함이 확인되었습니다. 즉, 무관한 GHSA-w42c-h7hr-f67p (v0.55.0)에 대한 수정이 반영된 이후에도 존재합니다.

추정 CVSS v3.1

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

  • AV:L — 이 프로젝트 자체의 MCP 표면에 대한 채점 선례와 일치합니다 (GHSA-w42c-h7hr-f67p도 AV:L을 사용했습니다): 기본 배포는 동일 호스트의 에이전트 프로세스가 stdio를 통해 접근하는 로컬 MCP 데몬입니다; AV:N은 옵트인 --http-addr 모드에만 적용됩니다.
  • UI:R — MCP 클라이언트(LLM 에이전트, 또는 gortex call을 통한 사람)가 search_text / search_text regexp:true 호출을 발행해야 합니다. 심어진 심볼릭 링크가 포함된 인덱싱된 저장소에 대해 광범위한 리터럴(예: 단일 흔한 문자)이나 일치하는 정규식(.)만으로도 대상 파일을 덤프하기에 충분합니다 — 그 내용에 대한 비밀 지식이 필요하지 않습니다 — 따라서 진입 장벽은 낮지만, 단순 인덱싱과는 구별되는 별개의 행위입니다.
  • C:H — 데몬의 OS 사용자가 읽을 수 있는 모든 것(SSH 키, 클라우드 자격 증명 파일, 다중 저장소 데몬에서 형제 테넌트의 저장소 등)에 대한 임의 파일 콘텐츠 노출로, 저장소 경계에 제한되지 않으며, 도구 응답에 그대로 반환됩니다.
  • I:N / A:N — 순수한 읽기 경로 버그입니다; 아무것도 기록되거나 사용 불가능해지지 않습니다.

세부 사항

근본 원인 1 — 인덱서 순회가 심볼릭 링크된 파일 항목을 검사하지 않음

internal/indexer/indexer.go:2549-2604, 주요 코퍼스 허용 순회:

root@kitploit:~
err = filepath.WalkDir(absRoot, func(path string, d os.DirEntry, err error) error {
    if err != nil { return nil }
    if d.IsDir() {
        if idx.shouldPruneDir(path, absRoot) { return filepath.SkipDir }
        return nil
    }
    lang, ok := idx.effectiveLanguage(path, nil)   // detects by extension; symlink name is enough
    if !ok { return nil }
    if idx.shouldExclude(path, absRoot, false) { return nil }
    info, statErr := d.Info()                      // Lstat'd DirEntry.Info() — no ModeSymlink check
    ...
    files = append(files, walkedFile{path: path, lang: lang, size: info.Size(), mtimeNano: info.ModTime().UnixNano()})
    return nil
})

filepath.WalkDir는 Lstat 시맨틱을 사용하므로 심볼릭 링크된 디렉터리는 자연스럽게 보호됩니다 — d.IsDir()가 false이고 재귀적으로 들어가지 않습니다. 심볼릭 링크된 파일 항목은 이에 상응하는 보호를 받지 못합니다: 여기서도 (또는 gitignore 스타일 경로 패턴만 일치시키는 shouldExclude/shouldPruneDir, internal/indexer/indexer.go:5724-5813에서도) 파일 항목을 큐에 넣기 전에 d.Type()&os.ModeSymlink, os.Lstat, 또는 filepath.EvalSymlinks를 호출하지 않습니다. 예를 들어 대상이 /etc/passwd, ~/.ssh/id_rsa, 또는 형제 테넌트 저장소인 pwn.go라는 이름의 파일은 단지 그 이름이 등록된 확장자와 일치한다는 이유만으로 허용됩니다 (effectiveLanguage, internal/parser DetectLanguageContent, 경로 기반). 이 정확한 순회된 파일 목록이 idx.fileMtimes가 됩니다 (internal/indexer/indexer.go:3760: idx.fileMtimes[idx.relKey(f.path)] = f.mtimeNano).

근본 원인 2 — search_text 싱크가 격리 가드 없이 실시간 파일을 재읽기함

internal/indexer/grep.go:17-64 (GrepText / warmTrigramSearcher)는 idx.fileMtimes의 키로부터 직접 trigram 검색기를 구축합니다:

root@kitploit:~
rels := ... // from idx.fileMtimes, i.e. includes the symlinked file's rel path
idx.trigramSearcher = trigram.Build(root, rels)

internal/search/trigram/searcher.go:32-48 (Build)와 :55-86/:102-176 (Grep/GrepRegexp)는 그런 다음 다음을 수행합니다:

root@kitploit:~
content, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(rel)))  // Build — follows symlink
...
f, err := os.Open(filepath.Join(s.root, filepath.FromSlash(rel)))          // Grep/GrepRegexp — follows symlink

os.ReadFile/os.Open은 기본적으로 심볼릭 링크를 추적합니다 (O_NOFOLLOW 없음). 이 함수들 중 어느 것도, internal/mcp/tools_search_text.go:35-126 (handleSearchText, s.indexer.GrepText/GrepRegexp를 호출하는 MCP 핸들러)도 resolveFilePath나 guardSymlinkWithinRepo를 호출하지 않습니다. 이 두 격리 함수는 존재하며 다른 모든 콘텐츠 제공 경로에 올바르게 연결되어 있습니다 — internal/mcp/tools_fileops.go (read_file, v0.55.0 수정 이후의 write_file/edit_file), internal/mcp/tools_coding.go:919 (get_symbol_source), internal/mcp/tools_lsp.go:264, internal/mcp/tools_export.go:95 — 그러나 search_text의 trigram grep 경로는 동일한 가드를 받은 적이 없는 별개의 싱크입니다. 트리 전체에서 grep -rn guardSymlinkWithinRepo는 tools_search_text.go, grep.go, 또는 trigram/searcher.go에 참조가 전혀 없음을 확인합니다.

중복 위험 / 구별성 검사

이것은 GHSA-w42c-h7hr-f67p와 진정으로 구별됩니다:

  • 해당 권고: 쓰기 경로, resolveFilePath가 절대 경로를 면제 + write_file/edit_file/move_inline이 guardSymlinkWithinRepo를 호출하지 않음 (v0.55.0에서 수정됨).
  • 이 발견: 읽기 경로, 인덱서의 자체 발견 순회(어떤 MCP 핸들러도 아님)가 Lstat 검사 없이 심볼릭 링크된 파일을 허용하고, 다른 MCP 도구(read_file/get_symbol_source가 아닌 search_text)가 독립적으로 guardSymlinkWithinRepo를 호출하지 않는 싱크(internal/search/trigram)를 통해 그 콘텐츠를 제공합니다. 쓰기 경로 수정은 이 두 위치 중 어느 것도 건드리지 않았습니다. 이는 ozgurcd/gograph (GHSA-6h6w-vhgr-2hp6)와 vitali87/code-graph-rag (GHSA-85gg-2gfq-q95m)에서 이미 확인된 형제 도구 패턴과 일치합니다: 심볼릭 링크 보호가 없는 파일 발견 순회와, 독립적으로 격리 가드가 없는 콘텐츠 제공 싱크의 짝.

개념 증명

정적 추적(위의 file:line 참조)은 완전하며 검사로 재현 가능합니다. 동적 검증은 시도되었으나 이 샌드박스의 툴체인 격차로 인해 실제로 차단되었으며, 이를 감추지 않고 정직하게 문서화합니다:

root@kitploit:~
$ CGO_ENABLED=1 go test ./internal/mcp/... -run TestPoC_SymlinkFollow_SearchTextDisclosesOutOfRepoContent -v
mcp [build failed]
  cgo: C compiler "gcc" not found: exec: "gcc": executable file not found in $PATH

이 코드베이스의 모든 tree-sitter 언어 추출기(일반 Go 추출기 포함, github.com/tree-sitter/tree-sitter-go의 bindings/go/binding.go: // #cgo CFLAGS: -std=c11 -fPIC / import "C")는 컴파일에 cgo를 필요로 합니다 — 단일 언어를 등록하는 순수 Go 코드 경로조차 없으므로, 작동하는 C 컴파일러 없이는 parser.Registry를 구축할 수 없습니다. 이 샌드박스에는 $PATH에 gcc/cc/clang이 없고, apt-get install gcc(apt 캐시에 존재, gcc-16/kali-last-snapshot, 그러나 권한 없이는 접근 불가)로 설치할 비밀번호 없는 root(sudo -l이 대화형 비밀번호를 요구)도 없습니다. 이는 환경 제한이며, gortex에 대한 발견이 아닙니다.

PoC 테스트 자체는 — 프로젝트 자체의 기존 테스트 하네스 패턴(internal/mcp/tools_search_text_test.go의 TestSearchText, 그리고 이전 권고의 자체 PoC가 사용한 것과 동일한 newSingleRepoServer/callTool 형태)에 맞춰 작성됨 — 유지관리자가 C 툴체인이 있는 환경에서 실행할 수 있도록 internal/mcp/poc_symlink_search_disclosure_test.go에 남겨둡니다:

root@kitploit:~
func TestPoC_SymlinkFollow_SearchTextDisclosesOutOfRepoContent(t *testing.T) {
	outsideDir := t.TempDir()
	secretPath := filepath.Join(outsideDir, "secret.txt")
	const secretMarker = "GORTEX-POC-SECRET-OUTSIDE-REPO-9f3a1c"
	os.WriteFile(secretPath, []byte("BEGIN OUTSIDE-REPO FILE\n"+secretMarker+"\nEND OUTSIDE-REPO FILE\n"), 0o600)

	repoDir := t.TempDir()
	os.WriteFile(filepath.Join(repoDir, "main.go"), []byte("package app\n\nfunc Hello() {}\n"), 0o644)
	os.Symlink(secretPath, filepath.Join(repoDir, "pwn.go"))   // symlink INSIDE repo -> OUTSIDE target

	idx := indexer.New(g, reg, cfg.Index, zap.NewNop())
	idx.Index(repoDir)                                          // walk admits pwn.go, no symlink check
	srv := NewServer(eng, g, idx, nil, zap.NewNop(), nil)

	res := callTool(t, srv, "search_text", map[string]any{"query": secretMarker})
	// asserts: res.IsError == false, exactly 1 match, Path == "pwn.go",
	// Text contains secretMarker — i.e. the OUTSIDE file's real content.
}

위의 소스 수준 추적을 고려하면 — 모든 단계가 정확한 함수로 명명되고 filepath.WalkDir에서 trigram.Build/Grep을 거쳐 handleSearchText의 응답에 이르는 경로 어디에도 격리 호출이 없음 — 이는 PoC 증거를 조작하지 않는다는 이 감사의 기본 규칙에 따라 정적으로 추적됨, 동적으로 확인되지 않음으로 제시됩니다.

영향

대상이 해당 저장소 루트 외부로 해석되는 심볼릭 링크를 포함하는 모든 인덱싱된 저장소(악의적인 기여자가 심어놓았거나, 복제된 의존성/플러그인 저장소에 존재하거나, 데몬이 나중에 인덱싱하는 공급망 PR을 통해 도입됨)는 MCP 클라이언트 — 일반 search_text 호출, 또는 프롬프트 주입/적대적 호출, GHSA-w42c-h7hr-f67p와 동일한 위협 모델 — 가 프로젝트가 read_file과 get_symbol_source에 대해 적용하는 격리 가드와 무관하게 해당 대상 파일의 내용을 그대로 읽을 수 있게 합니다. 다중 저장소 데몬(internal/indexer/multi.go:3525-3562, 추적된 저장소별로 동일한 무가드 호출을 확장함)에서는 동일한 데몬 인스턴스 내에서 테넌트/저장소 경계도 넘습니다.

취약점

  • CWE-59 — 파일 접근 전 부적절한 링크 해석('링크 추적'): 인덱서 순회(internal/indexer/indexer.go:2549)가 Lstat/ModeSymlink 검사 없이 심볼릭 링크된 파일 항목을 허용합니다.
  • CWE-200 — 비인가 행위자에게 민감 정보 노출: search_text (internal/mcp/tools_search_text.go) → trigram.Build/Grep/GrepRegexp (internal/search/trigram/searcher.go)가 동일 코드베이스의 다른 모든 읽기 싱크와 달리 guardSymlinkWithinRepo 격리 검사 없이 심볼릭 링크가 해석된 콘텐츠를 제공합니다.

수정

프로젝트 자체의 기존 guardSymlinkWithinRepo (internal/mcp/tools_fileops.go:353) — 또는 동등한 Lstat 기반 검사 — 를 두 지점에 적용하십시오:

  1. 순회 시점: filepath.WalkDir 콜백(internal/indexer/indexer.go:2549)에서 d.Type()&os.ModeSymlink != 0인 파일 DirEntry를 거부하거나 건너뛰거나(Walk가 디렉터리에 이미 부여하는 암묵적 보호를 반영), files에 추가하기 전에 filepath.EvalSymlinks(path)를 해석하여 absRoot 내 포함을 확인하십시오.
  2. 읽기 시점(심층 방어): trigram.Build/Grep/GrepRegexp (internal/search/trigram/searcher.go)에서, 또는 handleSearchText 호출 지점에서, 일치 항목의 Text를 반환하기 전에 read_file/get_symbol_source/tools_lsp/tools_export가 이미 사용하는 것과 동일한 guardSymlinkWithinRepo 스타일의 실제 경로 포함 검사를 적용하십시오. 어느 수정이든 단독으로 이 문제를 닫습니다; 둘 다 함께 적용하면 나머지 코드베이스가 이미 스스로에게 요구하는 격리 태세와 일치합니다.

크레딧

Dostxodjayev Abdullox (GitHub: @squeeze440)

보고 채널

비공개 취약점 보고(PVR)가 zzet/gortex에서 활성화되어 있고 확인되었습니다; 표준 GitHub 보안 권고(GHSA) 초안 권고 흐름이 적용되며, 이는 프로젝트의 이전 GHSA-w42c-h7hr-f67p 공개와 일치합니다.

도구 다운로드