
Consul Template validaba hacia dónde apuntaba un enlace simbólico durante la evaluación de la plantilla, pero su posterior obtención de dependencias leía la ruta original. Redirigir el enlace entre esas operaciones convirtió una referencia a un archivo dentro del sandbox en una divulgación de archivos fuera del sandbox.
Consul Template validaba a dónde apuntaba un enlace simbólico durante la evaluación de la plantilla, pero su posterior obtención de dependencias leía la ruta original. Reorientar el enlace entre esas operaciones convertía una referencia a un archivo dentro del sandbox en una divulgación de archivo fuera del sandbox.
Encontré este problema mientras revisaba HashiCorp Consul Template, con una pregunta de seguridad muy concreta en mente:
Si sandbox_path valida un enlace simbólico durante la evaluación de la plantilla, ¿la lectura posterior del archivo permanece vinculada a ese mismo objetivo validado?
En este caso, la respuesta fue no.
El asistente de plantilla file resolvía la ruta y aplicaba el sandbox configurado durante la evaluación de la plantilla. Pero después de esa comprobación, creaba una dependencia de archivo usando la ruta cruda original.
Esa dependencia se obtenía más tarde.
Si un atacante reorientaba el enlace simbólico en el intervalo entre la validación y la obtención de la dependencia, Consul Template podía leer un archivo fuera del sandbox. Si el enlace se restauraba antes de la siguiente renderización, la validación del sandbox volvía a pasar y el contenido externo almacenado en caché se seguía renderizando.
Ese problema se convirtió en CVE-2026-5061.
Boletín de HashiCorp: HCSEC-2026-12
Boletín de IBM: Boletín de seguridad CVE-2026-5061
CVE: CVE-2026-5061
Corregido en: 0.42.0
photo0
enlace simbólico dentro del sandbox controlado por el atacante -> la validación del sandbox resuelve a un objetivo seguro -> la dependencia almacena la ruta cruda original -> el atacante reorienta el enlace fuera del sandbox -> la obtención de la dependencia lee el archivo externo -> el atacante restaura el enlace seguro -> la siguiente validación pasa -> el contenido externo almacenado en caché se renderiza
Consul Template es una herramienta de renderizado de plantillas para datos de Consul y Vault.
Puede ejecutarse de forma continua, vigilar los cambios de las dependencias y renderizar datos en archivos o variables de entorno para que las aplicaciones los consuman.
El asistente de plantilla file lee un archivo local e inserta su contenido en la salida renderizada.
Dado que las lecturas de archivos locales pueden exponer secretos legibles por el proceso, Consul Template proporciona sandbox_path como límite para este asistente.
La propiedad de seguridad documentada es sencilla:
file deben estar dentro del sandbox configuradoEso convierte a sandbox_path en un límite de seguridad real.
La pregunta importante no era si la ruta parecía estar dentro del sandbox.
La pregunta real era:
¿El archivo que se lee sigue siendo el mismo archivo que pasó la validación del sandbox?
En las versiones vulnerables, no lo era.
Los sandboxes de sistemas de archivos suelen fallar en el intervalo entre la validación de la ruta y el acceso al archivo.
El patrón común es:
Esa suposición no es segura cuando un atacante puede modificar un enlace simbólico o una redirección equivalente del sistema de archivos entre las dos operaciones.
Consul Template hacía esta superficie especialmente interesante porque la evaluación de la plantilla y la obtención de dependencias eran etapas separadas.
Esa separación creaba la pregunta de seguridad adecuada:
¿La obtención de la dependencia está vinculada al objetivo validado, o vuelve a resolver la ruta controlada por el atacante?
Ese fue el límite en el que me centré.
La causa raíz era un desajuste de tiempo de comprobación frente a tiempo de uso entre:
template/funcs.godependency/file.goEn la revisión probada, fileFunc() hacía esto:
normalized := strings.TrimSpace(s)
err := pathInSandbox(sandboxPath, normalized)
if err != nil {
return "", err
}
d, err := dep.NewFileQuery(s)
El asistente de validación resolvía correctamente los enlaces simbólicos antes de comprobar la contención:
sandboxResolved, err := filepath.EvalSymlinks(filepath.Clean(sandbox))
targetResolved, err := filepath.EvalSymlinks(filepath.Clean(path))
rel, err := filepath.Rel(sandboxResolved, targetResolved)
if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return fmt.Errorf("'%s' is outside of sandbox", path)
}
Por lo tanto, la comprobación del sandbox entendía el objetivo resuelto.
Pero ese objetivo resuelto se descartaba.
NewFileQuery() almacenaba la cadena original en su lugar:
return &FileQuery{
stopCh: make(chan struct{}, 1),
path: s,
}, nil
Después, la obtención de la dependencia leía esa ruta de nuevo:
data, err := os.ReadFile(d.path)
Esa es toda la vulnerabilidad.
El código comprobaba una resolución del sistema de archivos y luego usaba otra distinta.
Porque un enlace simbólico es mutable.
El atacante no necesita hacer que un objetivo fuera del sandbox pase directamente pathInSandbox().
Solo necesita cambiar lo que la ruta cruda ya aprobada resuelve después de la validación, pero antes de que Fetch() realice la lectura.
La secuencia es:
pathInSandbox()FileQuery registra la ruta cruda del enlaceos.ReadFile(d.path) vuelve a resolver el enlaceEse último paso es importante.
Restaurar el enlace seguro no eliminaba el secreto ya obtenido de la caché de dependencias.
La distinción importante es la omisión de un control de seguridad explícito.
Esto no era simplemente:
"el archivo cambió mientras Consul Template lo estaba vigilando"
Vigilar los cambios de archivos es un comportamiento esperado.
El problema real era:
una ruta que pasaba la restricción documentada del sandbox podía usarse posteriormente para leer un archivo fuera de ese sandbox
Eso es un fallo directo del límite de confianza.
La aplicación ya había tomado una decisión de seguridad:
Pero la obtención posterior no estaba vinculada al objetivo que justificaba esa decisión.
Eso es lo que convirtió la mutabilidad ordinaria del sistema de archivos en una vulnerabilidad.
Construí un reproductor independiente alrededor de la secuencia exacta de evaluación y obtención de dependencias.
El entorno controlado usaba: