Reports
MEDIA[PATH TRAVERSAL]#3634571

Path traversal en protodump: escribir ficheros fuera del directorio de salida burlando una comprobación con HasPrefix

protodump confiaba en strings.HasPrefix para impedir escrituras fuera del directorio de salida. Un descriptor con «../out_pwn/» lo burlaba y permitía crear o sobrescribir .proto en un directorio vecino cuyo nombre empieza igual.

Resumen
Resumen en castellano de un reporte público, no una traducción literal. El código y los comandos se mantienen como en el original.

Resumen

protodump es una herramienta escrita en Go que procesa descriptores de Protocol Buffers contenidos en un fichero de entrada y los vuelca como ficheros .proto en un directorio de salida elegido por el usuario. El problema está en cómo decide dónde escribir cada fichero.

El nombre del fichero de salida se construye a partir de datos que vienen del propio descriptor que se está analizando (su name y la opción go_package), es decir, de la entrada que controla quien prepara el fichero. Para evitar que esa ruta se salga del directorio de salida, el código resuelve los enlaces simbólicos con EvalSymlinks y después comprueba:

strings.HasPrefix(base, outputDirAbs)

Esa comprobación es puramente textual: solo mira si la cadena de la ruta empieza por la cadena del directorio de salida, no si está realmente dentro de él. Si la salida es /tmp/out, la ruta /tmp/out_pwn/evil.proto también empieza por /tmp/out, así que pasa el filtro aunque sea un directorio hermano. Con un ../ en el nombre del descriptor basta para llegar ahí.

El investigador señaló la cadena completa en el código:

  1. pkg/protodump/proto.go (líneas 47-55): Filename() compone la ruta relativa con go_package y la ruta del descriptor, ambos controlados por el atacante.
  2. cmd/protodump/main.go (líneas 98-100): ese filename se pasa tal cual a writeFile(...).
  3. cmd/protodump/main.go (líneas 32-35): los directorios se calculan con path.Clean(fileDir) y se unen al de salida, de modo que los ../ acaban moviendo la ruta hacia el padre o un hermano.
  4. cmd/protodump/main.go (líneas 41-47): la contención se comprueba con strings.HasPrefix(base, outputDirAbs).
  5. cmd/protodump/main.go (líneas 51-58): tras el filtro, MkdirAll y WriteFile crean y escriben la ruta final.

Pasos de reproducción

  1. Compilar protodump.
  2. Generar un fichero de entrada cuyo descriptor se llame ../out_pwn/evil.proto:
name = b'../out_pwn/evil.proto'
with open('/tmp/evil.bin', 'wb') as f:
    f.write(bytes([0x0a, len(name)]) + name + b'\x00')
  1. Crear el directorio de salida y un directorio hermano cuyo nombre empiece por el mismo prefijo:
mkdir -p /tmp/out /tmp/out_pwn
  1. Ejecutar la herramienta indicando /tmp/out como salida. La propia herramienta informa de que ha escrito en el directorio vecino:
/tmp/protodump -file /tmp/evil.bin -output /tmp/out
Wrote /tmp/out_pwn/evil.proto
  1. Comprobar el resultado con ls /tmp/out y ls /tmp/out_pwn: /tmp/out sigue vacío y evil.proto ha aparecido en /tmp/out_pwn.

Impacto

Si una víctima ejecuta protodump sobre un fichero preparado por un atacante, este puede crear o sobrescribir ficheros .proto fuera de la carpeta de salida elegida, en directorios vecinos cuyo nombre comparta prefijo con ella. Eso compromete la integridad de los artefactos de proyectos adyacentes. La condición necesaria es que exista ese directorio hermano con prefijo coincidente.

El reporte se clasificó con severidad media, se envió el 28 de marzo de 2026, consta como resuelto y se divulgó el 31 de marzo de 2026.

Qué aprender de este caso

  • Busca strings.HasPrefix (o startsWith en otros lenguajes) usado para decidir si una ruta está dentro de un directorio: si no se compara contra el directorio terminado en separador (/tmp/out/), cualquier hermano como /tmp/out_pwn lo burla.
  • En Go, la comprobación correcta es calcular filepath.Rel(outputDir, destino) y rechazar el resultado si empieza por .., o comparar con outputDir + string(os.PathSeparator); desde Go 1.24 también existe os.Root para limitar las escrituras a un directorio.
  • En herramientas que convierten un fichero en un árbol de ficheros (extractores, descompiladores, volcadores como este), revisa de dónde sale cada nombre de salida: si viene de metadatos del propio fichero analizado, es entrada del atacante.
  • Al probar el filtro, no te quedes en ../../etc: prueba saltos a directorios hermanos que compartan prefijo con la carpeta de destino, que son los que sobreviven a un HasPrefix.