Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
gortex-PoC — PoC — seguir link simbólico para divulgação de conteúdo fora do repositório via search_text no Gortex (GHSA-6vhf-4wcm-2r83, CVE-2026-87003, CVSS 5.5). | Kitploit
Ferramentas/GitHubGitHub/squeeze440/gortex-poc
Análise EstáticaAnálise de VulnerabilidadesAnálise de CódigoExploraçãoColeta de InformaçõesSegurança WebPapers e Pesquisa
GitHubsqueeze440/gortex-poc

gortex-PoC

PoC — seguir link simbólico para divulgação de conteúdo fora do repositório via search_text no Gortex (GHSA-6vhf-4wcm-2r83, CVE-2026-87003, CVSS 5.5).

Ver Repositório
há 8 diasAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Seguimento de Symlink na Caminhada de Arquivos do Indexador → Divulgação de Conteúdo Fora do Repositório via search_text

Status do CVE: solicitado, aguardando atribuição. Esta descoberta é publicada como GHSA-6vhf-4wcm-2r83. Após a atribuição do CVE, este repositório é renomeado CVE-YYYY-NNNNN-gortex-PoC e este banner é substituído pelo link do CVE.

PesquisadorDostxodjayev Abdullox (@squeeze440)
AvisoGHSA-6vhf-4wcm-2r83
CVSS 3.15.5 (Médio)
FraquezaCWE-59, CWE-200

Seguimento de Symlink na Caminhada de Arquivos do Indexador → Divulgação de Conteúdo Fora do Repositório via search_text

Resumo

Uma falha de resolução inadequada de links (CWE-59) no indexador de repositório do gortex permite que uma entrada de arquivo com symlink apontando para fora da raiz do repositório indexado seja silenciosamente percorrida, indexada em conteúdo e posteriormente servida literalmente — inclusive para um alvo totalmente fora da raiz — pelo sink de releitura ao vivo da ferramenta MCP search_text, que nunca aplica a verificação de confinamento guardSymlinkWithinRepo do próprio projeto.

Produto

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

Versão Testada

Commit b5a63e25c0719c8ce935e59abf85618e2b4b1e64 (git describe: v0.62.0-35-gb5a63e25), o HEAD do repositório no momento da auditoria. Confirmado ainda presente na última versão marcada v0.62.0, ou seja, após a correção do não relacionado GHSA-w42c-h7hr-f67p (v0.55.0) ter sido aplicada.

CVSS v3.1 Estimado

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

  • AV:L — corresponde ao próprio precedente de pontuação deste projeto para sua superfície MCP (GHSA-w42c-h7hr-f67p também usou AV:L): a implantação padrão é um daemon MCP local acessado via stdio pelo processo do agente no mesmo host; AV:N só se aplicaria ao modo opt-in --http-addr.
  • UI:R — requer que o cliente MCP (um agente LLM, ou um humano via gortex call) emita uma chamada search_text / search_text regexp:true. Um literal amplo (por exemplo, um único caractere comum) ou uma regex correspondente (.) contra um repositório indexado contendo o symlink plantado é suficiente para despejar o arquivo alvo — sem necessidade de conhecimento secreto de seu conteúdo — então é uma barra baixa, mas é uma ação distinta da mera indexação.
  • C:H — divulgação de conteúdo de arquivo arbitrário, sem limites pelas fronteiras do repositório, de qualquer coisa que o usuário do SO do daemon possa ler (chaves SSH, arquivos de credenciais de nuvem, repositórios de inquilinos irmãos em um daemon multi-repo, etc.), retornado literalmente na resposta da ferramenta.
  • I:N / A:N — este é um bug puramente de caminho de leitura; nada é escrito ou tornado indisponível.

Detalhes

Causa raiz 1 — a caminhada do indexador nunca verifica entradas de arquivo com symlink

internal/indexer/indexer.go:2549-2604, a caminhada primária de admissão de corpus:

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 usa semântica de Lstat, então um diretório com symlink é naturalmente protegido — d.IsDir() é falso para ele e nunca é recursado. Uma entrada de arquivo com symlink não recebe proteção equivalente: nada aqui (nem em shouldExclude/shouldPruneDir, internal/indexer/indexer.go:5724-5813, que apenas correspondem a padrões de caminho no estilo gitignore) chama d.Type()&os.ModeSymlink, os.Lstat ou filepath.EvalSymlinks na entrada de arquivo antes de enfileirá-la. Um arquivo chamado, por exemplo, pwn.go cujo alvo é /etc/passwd, ~/.ssh/id_rsa ou um repositório de inquilino irmão é admitido puramente porque seu nome corresponde a uma extensão registrada (effectiveLanguage, internal/parser DetectLanguageContent, baseado em caminho). Essa lista exata de arquivos percorridos torna-se idx.fileMtimes (internal/indexer/indexer.go:3760: idx.fileMtimes[idx.relKey(f.path)] = f.mtimeNano).

Causa raiz 2 — o sink search_text relê arquivos ao vivo sem guarda de confinamento

internal/indexer/grep.go:17-64 (GrepText / warmTrigramSearcher) constrói o buscador de trigramas diretamente a partir das chaves de 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) e :55-86/:102-176 (Grep/GrepRegexp) então fazem:

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 seguem symlinks por padrão (sem O_NOFOLLOW). Nem essas funções, nem internal/mcp/tools_search_text.go:35-126 (handleSearchText, o handler MCP que chama s.indexer.GrepText/GrepRegexp), jamais chamam resolveFilePath ou guardSymlinkWithinRepo. Essas duas funções de confinamento existem e estão corretamente conectadas a todos os outros caminhos de serviço de conteúdo — internal/mcp/tools_fileops.go (read_file, write_file/edit_file a partir da correção v0.55.0), internal/mcp/tools_coding.go:919 (get_symbol_source), internal/mcp/tools_lsp.go:264, internal/mcp/tools_export.go:95 — mas o caminho de grep de trigramas do search_text é um sink separado que nunca recebeu a mesma guarda. grep -rn guardSymlinkWithinRepo em toda a árvore confirma zero referências em tools_search_text.go, grep.go ou trigram/searcher.go.

Verificação de risco de duplicata / distinção

Isto é genuinamente distinto de GHSA-w42c-h7hr-f67p:

  • Aquele aviso: caminho de escrita, resolveFilePath isentando caminhos absolutos + write_file/edit_file/move_inline nunca chamando guardSymlinkWithinRepo (corrigido em v0.55.0).
  • Esta descoberta: caminho de leitura, a própria caminhada de descoberta do indexador (não qualquer handler MCP) admite um arquivo com symlink sem verificação de Lstat, e uma ferramenta MCP diferente (search_text, não read_file/get_symbol_source) serve seu conteúdo através de um sink (internal/search/trigram) que independentemente nunca chama guardSymlinkWithinRepo. A correção do caminho de escrita não tocou em nenhum desses dois locais. Isto corresponde ao padrão de ferramenta irmã já confirmado em ozgurcd/gograph (GHSA-6h6w-vhgr-2hp6) e vitali87/code-graph-rag (GHSA-85gg-2gfq-q95m): uma caminhada de descoberta de arquivos sem proteção de symlink, emparelhada com um sink de serviço de conteúdo que independentemente carece de uma guarda de confinamento.

Prova de Conceito

Rastreamento estático (referências arquivo:linha acima) está completo e é reproduzível por inspeção. A verificação dinâmica foi tentada e está genuinamente bloqueada por uma lacuna de toolchain neste sandbox, documentada honestamente em vez de mascarada:

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

Todo extrator de linguagem tree-sitter neste código (incluindo o extrator Go simples, github.com/tree-sitter/tree-sitter-go's bindings/go/binding.go: // #cgo CFLAGS: -std=c11 -fPIC / import "C") requer cgo para compilar — não há caminho de código puramente Go para registrar sequer uma única linguagem, então parser.Registry não pode ser construído sem um compilador C funcional. Este sandbox não tem gcc/cc/clang no $PATH e nenhum root sem senha (sudo -l exige uma senha interativa) para instalar um via apt-get install gcc (presente no cache do apt, gcc-16/kali-last-snapshot, mas inacessível sem privilégio). Esta é uma limitação do ambiente, não uma descoberta sobre o gortex.

O próprio teste PoC — escrito contra o padrão de harness de teste existente do projeto (internal/mcp/tools_search_text_test.go's TestSearchText, e a mesma forma newSingleRepoServer/callTool que o próprio PoC do aviso anterior usou) — é deixado no lugar em internal/mcp/poc_symlink_search_disclosure_test.go para o mantenedor executar em um ambiente com uma toolchain 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.
}

Dado o rastreamento em nível de código-fonte acima — cada passo nomeado por função exata e nenhuma chamada de confinamento em qualquer lugar no caminho de filepath.WalkDir através de trigram.Build/Grep até a resposta de handleSearchText — isto é apresentado como estaticamente rastreado, não dinamicamente confirmado, conforme a regra fundamental desta auditoria contra fabricar evidências de PoC.

Impacto

Qualquer repositório indexado contendo um symlink cujo alvo resolve para fora da raiz desse repositório (plantado por um contribuidor malicioso, presente em um repositório de dependência/plugin clonado, ou introduzido via um PR de cadeia de suprimentos que o daemon posteriormente indexa) permite que um cliente MCP — uma chamada normal de search_text, ou uma com injeção de prompt/adversarial, mesmo modelo de ameaça que GHSA-w42c-h7hr-f67p — leia o conteúdo literal desse arquivo alvo, independentemente da guarda de confinamento que o projeto de outra forma impõe em read_file e get_symbol_source. Em um daemon multi-repo (internal/indexer/multi.go:3525-3562, que distribui a chamada idêntica sem guarda por repositório rastreado) isto também cruza fronteiras de inquilino/repositório dentro da mesma instância de daemon.

Fraquezas

  • CWE-59 — Resolução Inadequada de Link Antes do Acesso ao Arquivo ('Seguimento de Link'): a caminhada do indexador (internal/indexer/indexer.go:2549) admite uma entrada de arquivo com symlink sem verificação de Lstat/ModeSymlink.
  • CWE-200 — Exposição de Informação Sensível a um Ator Não Autorizado: search_text (internal/mcp/tools_search_text.go) → trigram.Build/Grep/GrepRegexp (internal/search/trigram/searcher.go) servem o conteúdo resolvido por symlink sem verificação de confinamento guardSymlinkWithinRepo, ao contrário de todos os outros sinks de leitura no mesmo código.

Remediação

Aplique o próprio guardSymlinkWithinRepo existente do projeto (internal/mcp/tools_fileops.go:353) — ou uma verificação equivalente baseada em Lstat — em ambos os pontos:

  1. No momento da caminhada: no callback de filepath.WalkDir (internal/indexer/indexer.go:2549), rejeite ou pule um DirEntry de arquivo cujo d.Type()&os.ModeSymlink != 0 (espelhando a proteção implícita que Walk já dá a diretórios), ou resolva filepath.EvalSymlinks(path) e confirme o confinamento dentro de absRoot antes de anexar a files.
  2. No momento da leitura (defesa em profundidade): em trigram.Build/Grep/GrepRegexp (internal/search/trigram/searcher.go), ou no local de chamada de handleSearchText, aplique a mesma verificação de confinamento de caminho real no estilo guardSymlinkWithinRepo já usada por read_file/get_symbol_source/tools_lsp/tools_export antes de retornar o Text de uma correspondência. Qualquer uma das correções isoladamente fecha isto; ambas juntas correspondem à postura de confinamento que o resto do código já mantém para si.

Crédito

Dostxodjayev Abdullox (GitHub: @squeeze440)

Canal de Relato

O Relato Privado de Vulnerabilidades (PVR) está habilitado e confirmado em zzet/gortex; o fluxo padrão de rascunho de aviso do GitHub Security Advisory (GHSA) se aplica, consistente com a divulgação anterior do projeto GHSA-w42c-h7hr-f67p.

Baixar ferramenta