
Consul Template vérifiait où pointait un lien symbolique lors de l'évaluation du modèle, mais sa récupération ultérieure des dépendances lisait le chemin d'origine. En redirigeant le lien entre ces opérations, une référence de fichier dans le sandbox est devenue une divulgation de fichier hors du sandbox.
Consul Template validait l'endroit où pointait un lien symbolique pendant l'évaluation du template, mais sa récupération ultérieure de dépendance lisait le chemin d'origine. En redirigeant le lien entre ces deux opérations, une référence de fichier dans le bac à sable devenait une divulgation de fichier hors du bac à sable.
J'ai trouvé ce problème en examinant HashiCorp Consul Template, avec une question de sécurité très précise en tête :
Si sandbox_path valide un lien symbolique pendant l'évaluation du template, la lecture de fichier ultérieure reste-t-elle liée à cette même cible validée ?
Dans ce cas, la réponse était non.
L'assistant de template file résolvait le chemin et appliquait le bac à sable configuré pendant l'évaluation du template. Mais après cette vérification, il créait une dépendance de fichier en utilisant le chemin brut d'origine.
Cette dépendance était récupérée plus tard.
Si un attaquant redirigeait le lien symbolique dans l'intervalle entre la validation et la récupération de la dépendance, Consul Template pouvait lire un fichier hors du bac à sable. Si le lien était restauré avant le rendu suivant, la validation du bac à sable passait à nouveau et le contenu externe mis en cache était toujours rendu.
Ce problème est devenu CVE-2026-5061.
Bulletin HashiCorp : HCSEC-2026-12
Bulletin IBM : Bulletin de sécurité CVE-2026-5061
CVE : CVE-2026-5061
Corrigé dans : 0.42.0
photo0
lien symbolique contrôlé par l'attaquant dans le bac à sable -> la validation du bac à sable résout la cible sûre -> la dépendance stocke le chemin brut d'origine -> l'attaquant redirige le lien hors du bac à sable -> la récupération de la dépendance lit le fichier externe -> l'attaquant restaure le lien sûr -> la validation suivante passe -> le contenu externe mis en cache est rendu
Consul Template est un outil de rendu de templates pour les données Consul et Vault.
Il peut s'exécuter en continu, surveiller les modifications des dépendances et générer des données dans des fichiers ou des variables d'environnement pour que les applications les consomment.
L'assistant de template file lit un fichier local et insère son contenu dans la sortie rendue.
Comme les lectures de fichiers locaux peuvent exposer des secrets lisibles par le processus, Consul Template fournit sandbox_path comme limite pour cet assistant.
La propriété de sécurité documentée est simple :
file doivent se trouver dans le bac à sable configuréCela fait de sandbox_path une véritable frontière de sécurité.
La question importante n'était pas de savoir si le chemin semblait être dans le bac à sable.
La vraie question était :
Le fichier qui est lu reste-t-il le même fichier qui a passé la validation du bac à sable ?
Dans les versions vulnérables, ce n'était pas le cas.
Les bacs à sable du système de fichiers échouent souvent dans l'intervalle entre la validation du chemin et l'accès au fichier.
Le schéma courant est :
Cette supposition est dangereuse lorsqu'un attaquant peut modifier un lien symbolique ou une redirection équivalente du système de fichiers entre les deux opérations.
Consul Template rendait cette surface particulièrement intéressante car l'évaluation du template et la récupération des dépendances étaient des étapes distinctes.
Cette séparation créait la bonne question de sécurité :
La récupération de la dépendance est-elle liée à la cible validée, ou résout-elle à nouveau le chemin contrôlé par l'attaquant ?
C'était la frontière sur laquelle je me suis concentré.
La cause racine était une inadéquation de type time-of-check à time-of-use entre :
template/funcs.godependency/file.goÀ la révision testée, fileFunc() faisait ceci :
normalized := strings.TrimSpace(s)
err := pathInSandbox(sandboxPath, normalized)
if err != nil {
return "", err
}
d, err := dep.NewFileQuery(s)
L'assistant de validation résolvait correctement les liens symboliques avant de vérifier le confinement :
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)
}
La vérification du bac à sable comprenait donc la cible résolue.
Mais cette cible résolue était ignorée.
NewFileQuery() stockait la chaîne d'origine à la place :
return &FileQuery{
stopCh: make(chan struct{}, 1),
path: s,
}, nil
Puis la récupération ultérieure de la dépendance lisait à nouveau ce chemin :
data, err := os.ReadFile(d.path)
C'est toute la vulnérabilité.
Le code vérifiait une résolution du système de fichiers, puis en utilisait une autre plus tard.
Parce qu'un lien symbolique est mutable.
L'attaquant n'a pas besoin de faire passer une cible hors du bac à sable directement dans pathInSandbox().
Il lui suffit de modifier ce vers quoi le chemin brut déjà approuvé résout après la validation, mais avant que Fetch() n'effectue la lecture.
La séquence est :
pathInSandbox()FileQuery enregistre le chemin brut du lienos.ReadFile(d.path) résout à nouveau le lienCette dernière étape est importante.
Restaurer le lien sûr ne supprimait pas le secret déjà récupéré du cache des dépendances.
La distinction importante est le contournement d'un contrôle de sécurité explicite.
Il ne s'agissait pas simplement de :
« le fichier a changé pendant que Consul Template le surveillait »
Surveiller les modifications de fichiers est un comportement attendu.
Le vrai problème était :
un chemin qui avait passé la restriction de bac à sable documentée pouvait ensuite être utilisé pour lire un fichier hors de ce bac à sable
C'est une défaillance directe de la frontière de confiance.
L'application avait déjà pris une décision de sécurité :
Mais la récupération ultérieure n'était pas liée à la cible qui justifiait cette décision.
C'est ce qui a transformé la mutabilité ordinaire du système de fichiers en vulnérabilité.
J'ai construit un reproducteur autonome autour de la séquence exacte d'évaluation et de récupération de dépendance.
La configuration contrôlée utilisait :