Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
gortex-PoC — PoC — seguimiento de enlaces simbólicos para la divulgación de contenido fuera del repositorio mediante search_text en Gortex (GHSA-6vhf-4wcm-2r83, CVE-2026-87003, CVSS 5.5). | Kitploit
Herramientas/GitHubGitHub/squeeze440/gortex-poc
Análisis EstáticoAnálisis de VulnerabilidadesAnálisis de CódigoExplotaciónRecopilación de InformaciónSeguridad WebPapers e Investigación
GitHubsqueeze440/gortex-poc

gortex-PoC

PoC — seguimiento de enlaces simbólicos para la divulgación de contenido fuera del repositorio mediante search_text en Gortex (GHSA-6vhf-4wcm-2r83, CVE-2026-87003, CVSS 5.5).

Ver Repositorio
hace 7 díasAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Seguimiento de enlaces simbólicos en el recorrido de archivos del indexador → Divulgación de contenido fuera del repositorio mediante search_text

Estado del CVE: solicitado, asignación pendiente. Este hallazgo se publica como GHSA-6vhf-4wcm-2r83. Tras la asignación del CVE, este repositorio se renombra CVE-YYYY-NNNNN-gortex-PoC y este banner se reemplaza con el enlace del CVE.

InvestigadorDostxodjayev Abdullox (@squeeze440)
AvisoGHSA-6vhf-4wcm-2r83
CVSS 3.15.5 (Medio)
DebilidadCWE-59, CWE-200

Seguimiento de enlaces simbólicos en el recorrido de archivos del indexador → Divulgación de contenido fuera del repositorio mediante search_text

Resumen

Una falla de resolución incorrecta de enlaces (CWE-59) en el indexador de repositorios de gortex permite que una entrada de archivo con enlace simbólico que apunta fuera de la raíz del repositorio indexado sea recorrida silenciosamente, indexada en contenido y posteriormente servida de forma literal — incluso hacia un objetivo completamente fuera de la raíz — por el sumidero de relectura en vivo de la herramienta MCP search_text, que nunca aplica la comprobación de confinamiento guardSymlinkWithinRepo propia del proyecto.

Producto

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

Versión probada

Commit b5a63e25c0719c8ce935e59abf85618e2b4b1e64 (git describe: v0.62.0-35-gb5a63e25), el HEAD del repositorio en el momento de la auditoría. Se confirmó que sigue presente en la última versión etiquetada v0.62.0, es decir, después de que se aplicara la corrección del aviso no relacionado GHSA-w42c-h7hr-f67p (v0.55.0).

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

  • AV:L — coincide con el propio precedente de puntuación de este proyecto para su superficie MCP (GHSA-w42c-h7hr-f67p también usó AV:L): el despliegue predeterminado es un daemon MCP local alcanzado por stdio por el proceso del agente en el mismo host; AV:N solo aplicaría al modo opcional --http-addr.
  • UI:R — requiere que el cliente MCP (un agente LLM, o un humano mediante gortex call) emita una llamada search_text / search_text regexp:true. Un literal amplio (por ejemplo, un único carácter común) o una expresión regular coincidente (.) contra un repositorio indexado que contenga el enlace simbólico plantado es suficiente para volcar el archivo objetivo — sin necesidad de conocer su contenido secreto — por lo que el umbral es bajo, pero es una acción distinta de la mera indexación.
  • C:H — divulgación de contenido arbitrario de archivos, sin límites por las fronteras del repositorio, de cualquier cosa que el usuario del SO del daemon pueda leer (claves SSH, archivos de credenciales en la nube, repositorios de inquilinos hermanos en un daemon multi-repositorio, etc.), devuelta de forma literal en la respuesta de la herramienta.
  • I:N / A:N — esto es un bug puro de la ruta de lectura; no se escribe ni se deja indisponible nada.

Detalles

Causa raíz 1 — el recorrido del indexador nunca comprueba entradas de archivo con enlace simbólico

internal/indexer/indexer.go:2549-2604, el recorrido principal de admisión al 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, por lo que un directorio con enlace simbólico está naturalmente protegido — d.IsDir() es falso para él y nunca se recurre en él. Una entrada de archivo con enlace simbólico no recibe protección equivalente: nada aquí (ni en shouldExclude/shouldPruneDir, internal/indexer/indexer.go:5724-5813, que solo coinciden con patrones de ruta estilo gitignore) llama a d.Type()&os.ModeSymlink, os.Lstat o filepath.EvalSymlinks sobre la entrada de archivo antes de encolarla. Un archivo llamado, por ejemplo, pwn.go cuyo objetivo sea /etc/passwd, ~/.ssh/id_rsa o un repositorio de inquilino hermano es admitido únicamente porque su nombre coincide con una extensión registrada (effectiveLanguage, internal/parser DetectLanguageContent, basado en ruta). Esta lista exacta de archivos recorridos se convierte en idx.fileMtimes (internal/indexer/indexer.go:3760: idx.fileMtimes[idx.relKey(f.path)] = f.mtimeNano).

Causa raíz 2 — el sumidero search_text relee archivos en vivo sin ninguna protección de confinamiento

internal/indexer/grep.go:17-64 (GrepText / warmTrigramSearcher) construye el buscador de trigramas directamente a partir de las claves 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) y :55-86/:102-176 (Grep/GrepRegexp) entonces hacen:

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 siguen enlaces simbólicos por defecto (sin O_NOFOLLOW). Ni esas funciones, ni internal/mcp/tools_search_text.go:35-126 (handleSearchText, el manejador MCP que llama a s.indexer.GrepText/GrepRegexp), llaman nunca a resolveFilePath o guardSymlinkWithinRepo. Esas dos funciones de confinamiento existen y están correctamente conectadas en todas las demás rutas que sirven contenido — internal/mcp/tools_fileops.go (read_file, write_file/edit_file a partir de la corrección de v0.55.0), internal/mcp/tools_coding.go:919 (get_symbol_source), internal/mcp/tools_lsp.go:264, internal/mcp/tools_export.go:95 — pero la ruta de grep por trigramas de search_text es un sumidero separado al que nunca se le dio la misma protección. grep -rn guardSymlinkWithinRepo en todo el árbol confirma cero referencias en tools_search_text.go, grep.go o trigram/searcher.go.

Comprobación de riesgo de duplicado / distinción

Esto es genuinamente distinto de GHSA-w42c-h7hr-f67p:

  • Ese aviso: ruta de escritura, resolveFilePath eximiendo rutas absolutas + write_file/edit_file/move_inline sin llamar nunca a guardSymlinkWithinRepo (corregido en v0.55.0).
  • Este hallazgo: ruta de lectura, el propio recorrido de descubrimiento del indexador (no ningún manejador MCP) admite un archivo con enlace simbólico sin comprobación de Lstat, y una herramienta MCP diferente (search_text, no read_file/get_symbol_source) sirve su contenido a través de un sumidero (internal/search/trigram) que de forma independiente nunca llama a guardSymlinkWithinRepo. La corrección de la ruta de escritura no tocó ninguna de estas dos ubicaciones. Esto coincide con el patrón de herramientas hermanas ya confirmado en ozgurcd/gograph (GHSA-6h6w-vhgr-2hp6) y vitali87/code-graph-rag (GHSA-85gg-2gfq-q95m): un recorrido de descubrimiento de archivos sin protección de enlaces simbólicos, emparejado con un sumidero que sirve contenido que de forma independiente carece de una protección de confinamiento.

Prueba de concepto

El rastreo estático (referencias archivo:línea anteriores) está completo y es reproducible por inspección. La verificación dinámica se intentó y está genuinamente bloqueada por una carencia de herramientas en este sandbox, documentada honestamente en lugar de disimulada:

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

Todos los extractores de lenguajes tree-sitter en este código base (incluido el extractor simple de Go, github.com/tree-sitter/tree-sitter-go's bindings/go/binding.go: // #cgo CFLAGS: -std=c11 -fPIC / import "C") requieren cgo para compilar — no hay ninguna ruta de código en Go puro para registrar siquiera un único lenguaje, por lo que parser.Registry no puede construirse sin un compilador de C funcional. Este sandbox no tiene gcc/cc/clang en $PATH ni root sin contraseña (sudo -l exige una contraseña interactiva) para instalar uno mediante apt-get install gcc (presente en la caché de apt, gcc-16/kali-last-snapshot, pero inalcanzable sin privilegios). Esto es una limitación del entorno, no un hallazgo sobre gortex.

La propia prueba PoC — escrita siguiendo el patrón del propio arnés de pruebas existente del proyecto (internal/mcp/tools_search_text_test.go's TestSearchText, y la misma forma newSingleRepoServer/callTool que usó el propio PoC del aviso anterior) — se deja en su sitio en internal/mcp/poc_symlink_search_disclosure_test.go para que el mantenedor la ejecute en un entorno con una cadena de herramientas de 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 el rastreo a nivel de código fuente anterior — cada paso nombrado por función exacta y sin ninguna llamada de confinamiento en ningún punto de la ruta desde filepath.WalkDir pasando por trigram.Build/Grep hasta la respuesta de handleSearchText — esto se presenta como rastreado estáticamente, no confirmado dinámicamente, conforme a la regla básica de esta auditoría contra fabricar evidencia de PoC.

Impacto

Cualquier repositorio indexado que contenga un enlace simbólico cuyo objetivo resuelva fuera de la raíz de ese repositorio (plantado por un contribuyente malicioso, presente en un repositorio de dependencia/plugin clonado, o introducido mediante un PR de cadena de suministro que el daemon indexe después) permite a un cliente MCP — una llamada normal a search_text, o una inyectada por prompt/adversaria, mismo modelo de amenaza que GHSA-w42c-h7hr-f67p — leer el contenido literal de ese archivo objetivo, independientemente de la protección de confinamiento que el proyecto aplica por lo demás a read_file y get_symbol_source. En un daemon multi-repositorio (internal/indexer/multi.go:3525-3562, que replica la llamada idéntica sin protección por cada repositorio rastreado) esto también cruza fronteras de inquilino/repositorio dentro de la misma instancia del daemon.

Debilidades

  • CWE-59 — Resolución incorrecta de enlaces antes del acceso a archivos ('Link Following'): el recorrido del indexador (internal/indexer/indexer.go:2549) admite una entrada de archivo con enlace simbólico sin comprobación de Lstat/ModeSymlink.
  • CWE-200 — Exposición de información sensible a un actor no autorizado: search_text (internal/mcp/tools_search_text.go) → trigram.Build/Grep/GrepRegexp (internal/search/trigram/searcher.go) sirven el contenido resuelto por enlace simbólico sin comprobación de confinamiento guardSymlinkWithinRepo, a diferencia de todos los demás sumideros de lectura en el mismo código base.

Remediación

Aplique el propio guardSymlinkWithinRepo existente del proyecto (internal/mcp/tools_fileops.go:353) — o una comprobación equivalente basada en Lstat — en ambos puntos:

  1. En tiempo de recorrido: en el callback de filepath.WalkDir (internal/indexer/indexer.go:2549), rechace u omita un DirEntry de archivo cuyo d.Type()&os.ModeSymlink != 0 (reflejando la protección implícita que Walk ya da a los directorios), o resuelva filepath.EvalSymlinks(path) y confirme la contención dentro de absRoot antes de añadirlo a files.
  2. En tiempo de lectura (defensa en profundidad): en trigram.Build/Grep/GrepRegexp (internal/search/trigram/searcher.go), o en el punto de llamada de handleSearchText, aplique la misma comprobación de contención de ruta real estilo guardSymlinkWithinRepo que ya usan read_file/get_symbol_source/tools_lsp/tools_export antes de devolver el Text de una coincidencia. Cualquiera de las dos correcciones por sí sola cierra esto; ambas juntas igualan la postura de confinamiento que el resto del código base ya se exige a sí mismo.

Crédito

Dostxodjayev Abdullox (GitHub: @squeeze440)

Canal de reporte

El Reporte Privado de Vulnerabilidades (PVR) está habilitado y confirmado en zzet/gortex; se aplica el flujo estándar de borrador de aviso de seguridad de GitHub (GHSA), consistente con la divulgación previa del proyecto GHSA-w42c-h7hr-f67p.

Descargar herramienta