
El helper writeToFile de Consul Template abría directamente un destino proporcionado por el operador y seguía los componentes de ruta enlazados, permitiendo que la salida renderizada escapara del directorio previsto y sobrescribiera un archivo preexistente.
El helper writeToFile de Consul Template abría directamente un destino proporcionado por el operador y seguía los componentes de ruta con enlaces, lo que permitía que la salida renderizada escapara del directorio previsto y sobrescribiera un archivo preexistente.
Encontré este problema al revisar HashiCorp Consul Template, con una pregunta directa de seguridad del sistema de archivos en mente:
Si writeToFile recibe una ruta que parece estar dentro del directorio previsto, ¿verifica dónde escribirá realmente el sistema de archivos los datos?
En este caso, la respuesta fue no.
El helper de plantilla writeToFile abría directamente la ruta final proporcionada por el usuario mediante os.Create() u os.OpenFile().
Esas operaciones seguían enlaces simbólicos, junctions de directorio y redirecciones equivalentes del sistema de archivos ya presentes en la ruta de destino.
Eso significaba que la cadena de ruta podía permanecer bajo la raíz prevista por el operador mientras que la escritura real aterrizaba en otro lugar.
En mi prueba de concepto controlada, un directorio padre con enlace redirigió la salida renderizada fuera del árbol previsto y provocó que un archivo de destino preexistente fuera sobrescrito.
Ese problema se convirtió en CVE-2026-14361.
Boletín de HashiCorp: HCSEC-2026-20
Boletín de IBM: boletín de seguridad CVE-2026-14361
CVE: CVE-2026-14361
Corregido en: 0.42.1
photo0
el destino proporcionado por el operador aparece dentro de la raíz prevista -> el atacante coloca de antemano un componente padre o final con enlace -> writeToFile abre la ruta directamente -> el sistema de archivos resuelve la escritura fuera del directorio previsto -> el secreto renderizado es redirigido -> un objetivo preexistente puede ser sobrescrito
Consul Template renderiza datos de fuentes como Consul y Vault.
El helper writeToFile permite que una plantilla escriba contenido seleccionado en un archivo local separado mientras aplica un propietario, grupo y modo de permisos solicitados.
La documentación de HashiCorp demuestra específicamente el helper con material PKI:
private key -> writeToFile /my/path/to/cert.key
certificate authority -> writeToFile /my/path/to/cert.pem
certificate -> append to /my/path/to/cert.pem
Eso lo convierte en algo más que un helper de salida ordinario.
El contenido que cruza este límite puede incluir:
La pregunta importante no era si writeToFile podía crear un nombre de archivo solicitado.
La pregunta real era:
¿El proceso escribe en la ubicación del sistema de archivos prevista por el operador, o simplemente en el objeto al que la ruta se resuelve en el momento de la apertura?
En las versiones vulnerables, confiaba en esto último.
Los helpers de escritura son superficies de seguridad de alto valor porque cruzan de los datos de la aplicación a la mutación del sistema de archivos.
Los fallos interesantes a menudo no son el clásico traversal ../.
Son fallos de resolución:
Eso es especialmente importante cuando el proceso se ejecuta con más privilegios del sistema de archivos que el atacante.
Un atacante local con pocos privilegios puede no ser capaz de sobrescribir un archivo sensible directamente.
Pero si puede influir en un componente de ruta bajo un directorio de escritura previsto, un proceso de Consul Template más privilegiado puede realizar la escritura por él.
Ese fue el límite en el que me centré.
No abordé esto como una revisión genérica de path traversal.
La ruta proporcionada no necesitaba segmentos ...
Podía permanecer léxicamente dentro de la raíz prevista todo el tiempo.
La pregunta más sólida era:
¿Se rechazan los componentes de destino con enlaces antes de que se escriba contenido sensible?
Esa pregunta importa para ambos casos:
El segundo caso es especialmente útil porque los registros y la configuración siguen mostrando una ruta de apariencia inocente bajo el directorio esperado.
El sistema de archivos la resuelve en otro lugar.
La causa raíz era la creación directa de archivos basada en rutas sin validación consciente de enlaces.
En la revisión probada, writeToFile() seleccionaba uno de dos caminos de apertura.
El modo de append usaba:
f, err = os.OpenFile(
path,
os.O_APPEND|os.O_WRONLY|os.O_CREATE,
perm,
)
El modo de escritura normal usaba:
dirPath := filepath.Dir(path)
if _, err := os.Stat(dirPath); err != nil {
err := os.MkdirAll(dirPath, os.ModePerm)
if err != nil {
return "", err
}
}
f, err = os.Create(path)
Ninguno de los dos caminos rechazaba componentes de destino con enlaces antes de abrir el archivo.
Eso importa porque:
os.Create(path) sigue las redirecciones existentes del sistema de archivos y trunca el archivo resueltoos.OpenFile(path, ...) sigue los componentes de ruta con enlaces durante el modo appendLa misma suposición basada en rutas continuaba después de la escritura.
La propiedad y los permisos se aplicaban usando la ruta nuevamente:
err = os.Chown(path, uid, gid)
err = os.Chmod(path, perm)
Eso significaba que las operaciones de metadatos también estaban vinculadas a un nombre de ruta mutable en lugar del descriptor de archivo ya abierto.
Porque el atacante puede preparar la redirección antes de que writeToFile se ejecute.
No se requiere una condición de carrera probabilística para el ataque básico.
La secuencia es sencilla:
writeToFile la abre directamenteEsa es toda la vulnerabilidad.
Es cierto que los sistemas operativos normalmente siguen los symlinks durante las aperturas de archivos basadas en rutas.
Eso no hace que este sea un comportamiento seguro de la aplicación.
La pregunta de seguridad no es:
"¿Go se comportó como está documentado?"
La pregunta real es:
¿Un helper que escribe datos sensibles renderizados verificó que el destino resuelto coincidiera con la ubicación prevista por el operador?
En las versiones vulnerables, no lo hizo.
Esa distinción importa porque Consul Template puede ejecutarse:
Seguir una redirección del sistema de archivos posicionada por el atacante en ese contexto crea un problema real de privilegios y de límite de confianza.
Construí un reproductor independiente alrededor del comportamiento exacto de writeToFile en el commit probado.
La configuración controlada usaba:
writeToFileEl flujo de reproducción fue:
El comportamiento observado fue:
writeToFile siguió la redirecciónEso estableció ambas partes de la afirmación:
Un symlink en el componente final ya demostraría la práctica insegura de seguir enlaces.
Pero un componente padre con enlace prueba un punto operativo más sólido:
El objetivo preexistente también importaba.
Sin él, el PoC mostraría solo una creación inesperada de archivos.
Al comenzar con un archivo existente y verificar su hash final, la reproducción demostró directamente el comportamiento de sobrescritura.
Eso hizo que el resultado fuera más concreto que una afirmación solo basada en el código fuente.
Este problema requiere influencia local en el sistema de archivos.
Un atacante necesita suficiente acceso para crear o modificar un symlink, junction de directorio o redirección equivalente en o debajo de la ubicación de escritura prevista.
El proceso de Consul Template debe entonces escribir a través de esa ruta.
El impacto depende en gran medida de los privilegios del proceso y del contenido renderizado.
Los resultados prácticos incluyen:
Esto no era una escritura arbitraria remota no autenticada desde una instalación predeterminada.
Pero era un claro fallo local de límite de confianza con impacto significativo en implementaciones privilegiadas o con sistemas de archivos compartidos.
HashiCorp clasificó el problema como:
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N
La puntuación base publicada es:
4.7 / Medium
Ese vector refleja:
Los boletines oficiales también documentan explícitamente que la escritura redirigida puede sobrescribir un archivo preexistente.
La puntuación es moderada porque la explotación depende de condiciones locales de control de rutas, no porque el efecto en el sistema de archivos sea teórico.
El boletín de HashiCorp enumera:
Affected: consul-template up to and including 0.42.0
Fixed: consul-template 0.42.1
La corrección se publicó en la versión 0.42.1 el 8 de julio de 2026.
El parche 0.42.1 endureció la ruta de escritura en varias capas.
El helper parcheado usa os.Lstat() para inspeccionar:
y rechaza esos componentes cuando son enlaces.
Eso cierra los casos de redirección directa del padre y de symlink del archivo final cubiertos por el informe y las pruebas de regresión.
En plataformas Unix compatibles, el destino se abre con O_NOFOLLOW.
Eso hace que la propia apertura falle si el componente final se convierte en un symlink entre la verificación previa y la apertura.
Esto es importante porque una verificación previa por sí sola puede crear otra ventana TOCTOU.
La implementación específica de la plataforma es un no-op en Windows, donde O_NOFOLLOW no está disponible a través del mismo mecanismo.
El parche reemplazó las operaciones de propiedad y modo basadas en rutas por operaciones basadas en el descriptor:
f.Chown(uid, gid)
f.Chmod(perm)
Eso vincula los cambios de metadatos al archivo que realmente se abrió en lugar de resolver la ruta nuevamente más tarde.
El helper ahora crea el directorio padre solo cuando el fallo de stat es genuinamente os.IsNotExist.
Otros errores, como fallos de permisos, se devuelven en lugar de continuar hacia la ruta de escritura.
El parche añadió pruebas específicas para:
La discusión pública del parche documenta una limitación importante con claridad.
La nueva validación verifica:
No recorre ni rechaza todos los ancestros de nivel superior.
Los mantenedores eligieron ese límite porque writeToFile no tiene una raíz de sandbox configurada para anclar una verificación completa de contención, y los sistemas operativos comunes pueden incluir enlaces gestionados legítimos en prefijos de ruta, como /var -> /private/var en macOS.
O_NOFOLLOW también protege el componente final, no todos los directorios ancestros.
Eso no cambia el estado de la vulnerabilidad reportada ni la versión oficial corregida.
Sí aclara la propiedad de seguridad exacta que entrega el parche:
Esa distinción vale la pena conservarla en un informe técnico.
Reporté este problema de forma privada a HashiCorp Security el 21 de marzo de 2026 como un segundo hallazgo independiente de Consul Template.
El informe incluía:
El correo de seguimiento original no estaba presente en la cola de informes del equipo de seguridad, probablemente porque fue atrapado por un filtro de spam de la lista de correo.
Después de que reenvié el informe completo, HashiCorp se puso en contacto con el equipo de ingeniería y lo investigó por separado del primer problema de Consul Template.
HashiCorp corrigió la vulnerabilidad en 0.42.1 y publicó HCSEC-2026-20 el 8 de julio de 2026.
IBM publicó un boletín de seguridad correspondiente para el mismo CVE.
Ambos boletines oficiales reconocieron el informe como:
Mohamed Abdelaal (0xmrma)
La lección clave es simple:
una cadena de ruta no es lo mismo que el objeto del sistema de archivos que nombra
Esa distinción importa siempre que un código privilegiado escribe rutas influenciadas por el atacante.
Verificar que una cadena comienza con un directorio previsto no es suficiente.
Incluso una ruta limpia sin secuencias de traversal puede resolverse en otro lugar a través de:
La operación sensible debe estar vinculada a un destino cuya identidad haya sido validada en el límite correcto.
Este problema también refuerza una regla más amplia:
cuando ya tienes un descriptor de archivo abierto, aplica las operaciones sensibles de seguridad a través de ese descriptor en lugar de resolver la ruta nuevamente
Eso es exactamente por qué los cambios de Chown y Chmod basados en el descriptor importan.
writeToFile está documentado para material sensible como certificados y claves privadasos.Create u os.OpenFileChown y Chmod basados en rutas introducían una confianza adicional en rutas mutables0.42.1 añadió verificaciones de enlaces, O_NOFOLLOW donde es compatible, operaciones de metadatos basadas en el descriptor y pruebas de regresiónEsta vulnerabilidad no se trataba de traversal ../.
La cadena de ruta parecía correcta.
El destino del sistema de archivos no lo era.
Consul Template aceptó la ruta prevista por el operador, siguió una redirección posicionada por el atacante y escribió el contenido renderizado en un archivo diferente.
Por eso se convirtió en CVE-2026-14361.
Corregido en consul-template 0.42.1.