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
静态分析漏洞分析代码分析漏洞利用信息收集Web安全论文与研究
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 (中危)
弱点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 (中危)

  • AV:L — 与本项目自身对其 MCP 表面的评分先例一致(GHSA-w42c-h7hr-f67p 也使用了 AV:L):默认部署是本地 MCP 守护进程,由同一主机上的代理进程通过 stdio 访问;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,且永远不会递归进入。符号链接文件条目则没有等效保护:此处(以及 shouldExclude/shouldPruneDir,internal/indexer/indexer.go:5724-5813,它们仅匹配 gitignore 风格的路径模式)没有任何代码在将文件条目排队之前对其调用 d.Type()&os.ModeSymlink、os.Lstat 或 filepath.EvalSymlinks。一个名为例如 pwn.go 的文件,其目标为 /etc/passwd、~/.ssh/id_rsa 或同级租户仓库,仅因其名称匹配已注册的扩展名(effectiveLanguage,internal/parser 的 ,基于路径)而被准入。这个确切的已遍历文件列表成为 (:)。

根本原因 2 — search_text 接收器实时重读文件,没有限制保护

internal/indexer/grep.go:17-64(GrepText / warmTrigramSearcher)直接从 idx.fileMtimes 的键构建三元组搜索器:

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、write_file/edit_file,自 v0.55.0 修复起)、internal/mcp/tools_coding.go:919()、、——但 的三元组 grep 路径是一个独立的接收器,从未获得相同的保护。在整个代码树中执行 确认在 、 或 中零引用。

重复风险 / 差异性检查

这与 GHSA-w42c-h7hr-f67p 确实不同:

  • 该公告:写入路径,resolveFilePath 豁免绝对路径 + write_file/edit_file/move_inline 从未调用 guardSymlinkWithinRepo(已在 v0.55.0 中修复)。
  • 本发现:读取路径,索引器自身的发现遍历(不是任何 MCP 处理器)在没有 Lstat 检查的情况下准入符号链接文件,并且一个不同的 MCP 工具(search_text,而非 read_file/get_symbol_source)通过一个独立地从未调用 guardSymlinkWithinRepo 的接收器(internal/search/trigram)提供其内容。写入路径修复未触及这两个位置中的任何一个。 这与已在 ozgurcd/gograph(GHSA-6h6w-vhgr-2hp6)和 vitali87/code-graph-rag()中确认的同类工具模式一致:一个没有符号链接保护的文件发现遍历,配对一个独立地缺乏限制保护的内容服务接收器。

概念验证

静态追踪(上文中的 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,也没有免密 root(sudo -l 要求交互式密码)来通过 apt-get install gcc 安装一个(apt 缓存中存在,gcc-16/kali-last-snapshot,但没有特权无法访问)。这是环境限制,而非关于 gortex 的发现。

PoC 测试本身——按照项目自身现有的测试框架模式编写(internal/mcp/tools_search_text_test.go 的 TestSearchText,以及先前公告自身 PoC 所使用的相同 newSingleRepoServer/callTool 形态)——留在 internal/mcp/poc_symlink_search_disclosure_test.go 中,供维护者在具有 C 工具链的环境中运行:

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 之前应用 /// 已使用的相同 风格的真实路径包含检查。 任一修复单独即可关闭此问题;两者结合则与代码库其余部分已自我要求的限制姿态相匹配。

致谢

Dostxodjayev Abdullox(GitHub:@squeeze440)

报告渠道

私有漏洞报告(PVR)已在 zzet/gortex 上启用并确认;适用标准的 GitHub 安全公告(GHSA)草稿公告流程,与项目先前的 GHSA-w42c-h7hr-f67p 披露一致。

下载工具
DetectLanguageContent
idx.fileMtimes
internal/indexer/indexer.go:3760
idx.fileMtimes[idx.relKey(f.path)] = f.mtimeNano
get_symbol_source
internal/mcp/tools_lsp.go:264
internal/mcp/tools_export.go:95
search_text
grep -rn guardSymlinkWithinRepo
tools_search_text.go
grep.go
trigram/searcher.go
GHSA-85gg-2gfq-q95m
read_file
get_symbol_source
tools_lsp
tools_export
guardSymlinkWithinRepo