Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
gortex-PoC — PoC — переход по символической ссылке к раскрытию содержимого за пределами репозитория через search_text в Gortex (GHSA-6vhf-4wcm-2r83, CVE-2026-87003, CVSS 5.5). | Kitploit
Инструменты/GitHubGitHub/squeeze440/gortex-poc
Статический анализАнализ уязвимостейАнализ КодаЭксплуатацияСбор информацииВеб-безопасностьСтатьи и Исследования
GitHubsqueeze440/gortex-poc

gortex-PoC

PoC — переход по символической ссылке к раскрытию содержимого за пределами репозитория через search_text в Gortex (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

Краткое описание

Недостаток корректного разрешения ссылок (CWE-59) в индексаторе репозиториев gortex позволяет записи файла, являющейся символической ссылкой и указывающей за пределы корня индексируемого репозитория, быть незаметно обойдённой, проиндексированной по содержимому и впоследствии отданной дословно — в том числе для цели, полностью находящейся за пределами корня — через живой приёмник повторного чтения MCP-инструмента search_text, который никогда не применяет собственную проверку ограничения 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() для неё ложно, и в неё никогда не происходит рекурсивный спуск. Запись файла, являющегося символической ссылкой, не получает эквивалентной защиты: ничто здесь (ни в 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 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:

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, MCP-обработчик, вызывающий s.indexer.GrepText/GrepRegexp), никогда не вызывают resolveFilePath или guardSymlinkWithinRepo. Эти две функции ограничения существуют и корректно подключены ко всем остальным путям обслуживания содержимого — internal/mcp/tools_fileops.go (read_file, write_file/edit_file начиная с исправления v0.55.0), internal/mcp/tools_coding.go:919 (get_symbol_source), internal/mcp/tools_lsp.go:264, internal/mcp/tools_export.go:95 — но триграммный путь grep в search_text является отдельным приёмником, которому никогда не был дан тот же защитный механизм. 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-инструмент (search_text, а не read_file/get_symbol_source) обслуживает его содержимое через приёмник (internal/search/trigram), который независимо никогда не вызывает guardSymlinkWithinRepo. Исправление пути записи не затрагивало ни одно из этих двух мест. Это соответствует паттерну родственного инструмента, уже подтверждённому в ozgurcd/gograph (GHSA-6h6w-vhgr-2hp6) и vitali87/code-graph-rag (GHSA-85gg-2gfq-q95m): обход обнаружения файлов без защиты от символических ссылок в паре с приёмником обслуживания содержимого, который независимо лишён проверки ограничения.

Доказательство концепции

Статическая трассировка (ссылки файл:строка выше) является полной и воспроизводимой путём инспекции. Динамическая верификация была предпринята и действительно заблокирована пробелом в инструментальной цепочке в этой песочнице, что задокументировано честно, а не замаскировано:

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's bindings/go/binding.go: // #cgo CFLAGS: -std=c11 -fPIC / import "C") требует cgo для компиляции — нет чисто-Go пути кода для регистрации хотя бы одного языка, поэтому parser.Registry не может быть построен без работающего компилятора C. В этой песочнице нет gcc/cc/clang в $PATH и нет беспарольного root (sudo -l требует интерактивного пароля) для установки одного через apt-get install gcc (присутствует в кэше apt, gcc-16/kali-last-snapshot, но недостижим без привилегий). Это ограничение окружения, а не находка о gortex.

Сам PoC-тест — написанный по собственному существующему паттерну тестового стенда проекта (TestSearchText из internal/mcp/tools_search_text_test.go и та же форма newSingleRepoServer/callTool, которую использовал собственный PoC предыдущей рекомендации) — оставлен на месте в 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 — Improper Link Resolution Before File Access ('Link Following'): обход индексатора (internal/indexer/indexer.go:2549) допускает запись файла, являющегося символической ссылкой, без проверки Lstat/ModeSymlink.
  • CWE-200 — Exposure of Sensitive Information to an Unauthorized Actor: 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. Во время обхода: в callback filepath.WalkDir (internal/indexer/indexer.go:2549) отклоняйте или пропускайте DirEntry файла, у которого d.Type()&os.ModeSymlink != 0 (зеркально отражая неявную защиту, которую Walk уже даёт каталогам), или разрешите filepath.EvalSymlinks(path) и подтвердите нахождение внутри absRoot перед добавлением в files.
  2. Во время чтения (защита в глубину): в trigram.Build/Grep/GrepRegexp (internal/search/trigram/searcher.go) или в месте вызова handleSearchText примените ту же проверку ограничения реального пути в стиле guardSymlinkWithinRepo, уже используемую read_file/get_symbol_source/tools_lsp/tools_export, перед возвратом Text совпадения. Любое из исправлений по отдельности закрывает это; оба вместе соответствуют позиции ограничения, которой уже придерживается остальная часть кодовой базы.

Благодарность

Dostxodjayev Abdullox (GitHub: @squeeze440)

Канал отчётности

Private Vulnerability Reporting (PVR) включён и подтверждён на zzet/gortex; применяется стандартный процесс черновика GitHub Security Advisory (GHSA), согласующийся с предыдущим раскрытием GHSA-w42c-h7hr-f67p проекта.

Скачать инструмент