
Consul Template validava la destinazione di un symlink durante la valutazione del template, ma il successivo recupero delle dipendenze leggeva il percorso originale. Reindirizzare il link tra queste operazioni trasformava un riferimento a un file all'interno della sandbox in una divulgazione di file al di fuori della sandbox.
Consul Template ha convalidato dove puntava un symlink durante la valutazione del template, ma il successivo fetch della dipendenza leggeva il percorso originale. Ripuntare il collegamento tra queste due operazioni ha trasformato un riferimento a un file all'interno della sandbox in una divulgazione di file al di fuori della sandbox.
Ho trovato questo problema durante la revisione di HashiCorp Consul Template, con una domanda di sicurezza molto specifica in mente:
Se sandbox_path convalida un symlink durante la valutazione del template, la successiva lettura del file rimane vincolata allo stesso target convalidato?
In questo caso, la risposta è stata no.
L'helper di template file risolveva il percorso e applicava la sandbox configurata durante la valutazione del template. Ma dopo quel controllo, creava una dipendenza di file utilizzando il percorso grezzo originale.
Quella dipendenza veniva recuperata successivamente.
Se un attaccante ripuntava il symlink nell'intervallo tra la convalida e il fetch della dipendenza, Consul Template poteva leggere un file fuori dalla sandbox. Se il collegamento veniva ripristinato prima del rendering successivo, la convalida della sandbox passava di nuovo e il contenuto esterno memorizzato in cache veniva comunque renderizzato.
Quel problema è diventato CVE-2026-5061.
Bollettino HashiCorp: HCSEC-2026-12
Bollettino IBM: Bollettino di sicurezza CVE-2026-5061
0.42.0photo0
symlink in-sandbox controllato dall'attaccante -> la convalida della sandbox risolve un target sicuro -> la dipendenza memorizza il percorso grezzo originale -> l'attaccante ripunta il collegamento fuori dalla sandbox -> il fetch della dipendenza legge il file esterno -> l'attaccante ripristina il collegamento sicuro -> la convalida successiva passa -> il contenuto esterno in cache viene renderizzato
Consul Template è uno strumento di rendering di template per i dati di Consul e Vault.
Può essere eseguito in modo continuo, monitorare i cambiamenti delle dipendenze e renderizzare i dati in file o variabili d'ambiente per il consumo da parte delle applicazioni.
L'helper di template file legge un file locale e inserisce il suo contenuto nell'output renderizzato.
Poiché la lettura di file locali può esporre segreti leggibili dal processo, Consul Template fornisce sandbox_path come confine per questo helper.
La proprietà di sicurezza documentata è semplice:
file devono trovarsi all'interno della sandbox configurataQuesto rende sandbox_path un vero confine di sicurezza.
La domanda importante non era se il percorso sembrava essere all'interno della sandbox.
La vera domanda era:
Il file che viene letto rimane lo stesso file che ha superato la convalida della sandbox?
Nelle versioni vulnerabili, non lo era.
Le sandbox dei filesystem falliscono spesso nello spazio tra la convalida del percorso e l'accesso al file.
Il pattern comune è:
Questa assunzione non è sicura quando un attaccante può modificare un symlink o una ridirezione equivalente del filesystem tra le due operazioni.
Consul Template rendeva questa superficie particolarmente interessante perché la valutazione del template e il fetch delle dipendenze erano fasi separate.
Quella separazione creava la giusta domanda di sicurezza:
Il fetch della dipendenza è vincolato al target convalidato, oppure risolve di nuovo il percorso controllato dall'attaccante?
Era quel confine su cui mi sono concentrato.
La causa principale era una discrepanza time-of-check to time-of-use tra:
template/funcs.godependency/file.goAlla revisione testata, fileFunc() faceva questo:
normalized := strings.TrimSpace(s)
err := pathInSandbox(sandboxPath, normalized)
if err != nil {
return "", err
}
d, err := dep.NewFileQuery(s)
L'helper di convalida risolveva correttamente i symlink prima di verificare l'appartenenza alla sandbox:
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)
}
Quindi il controllo della sandbox comprendeva il target risolto.
Ma quel target risolto veniva scartato.
NewFileQuery() memorizzava invece la stringa originale:
return &FileQuery{
stopCh: make(chan struct{}, 1),
path: s,
}, nil
Poi il fetch successivo della dipendenza leggeva di nuovo quel percorso:
data, err := os.ReadFile(d.path)
Questa è l'intera vulnerabilità.
Il codice controllava una risoluzione del filesystem e successivamente ne usava un'altra.
Perché un symlink è mutabile.
L'attaccante non deve far superare a un target fuori dalla sandbox il controllo pathInSandbox() direttamente.
Deve solo cambiare ciò a cui il percorso grezzo già approvato risolve dopo la convalida, ma prima che Fetch() esegua la lettura.
La sequenza è:
pathInSandbox()FileQuery registra il percorso grezzo del collegamentoos.ReadFile(d.path) risolve di nuovo il collegamentoQuell'ultimo passaggio è importante.
Ripristinare il collegamento sicuro non rimuoveva il segreto già recuperato dalla cache delle dipendenze.
La distinzione importante è il bypass di un controllo di sicurezza esplicito.
Non si trattava semplicemente di:
"il file è cambiato mentre Consul Template lo stava osservando"
Osservare i file per rilevarne i cambiamenti è un comportamento previsto.
Il vero problema era:
un percorso che superava la restrizione documentata della sandbox poteva successivamente essere usato per leggere un file fuori da quella sandbox
Si tratta di un fallimento diretto del confine di fiducia.
L'applicazione aveva già preso una decisione di sicurezza:
Ma il fetch successivo non era legato al target che giustificava quella decisione.
È questo che ha trasformato la normale mutabilità del filesystem in una vulnerabilità.
Ho creato un reproducer autonomo basato sull'esatta sequenza di valutazione e fetch della dipendenza.
La configurazione controllata utilizzava:
Il flusso di riproduzione era:
file in modo che la convalida della sandbox riesca e il percorso grezzo venga registrato come dipendenza.os.ReadFile(d.path) di leggere attraverso il collegamento reindirizzato.Il comportamento osservato è stato:
Quel confronto hash era importante.
Dimostrava che il valore finale renderizzato non era contenuto sicuro obsoleto, un artefatto del nome file o un effetto collaterale del percorso di errore.
Era il contenuto esatto byte per byte del file fuori dalla sandbox.
La prova più forte per un problema TOCTOU deve controllare la timeline.
Dimostrare semplicemente che un symlink può puntare fuori dalla sandbox sarebbe più debole, perché pathInSandbox() rifiutava già un target esterno quando lo osservava.
La rivendicazione di sicurezza dipendeva dalla dimostrazione di tutti questi stati in ordine:
È per questo che il reproducer separava esplicitamente la convalida del template, il fetch della dipendenza, il ripristino del collegamento e il rendering successivo.
Il PoC non si basava su tempistiche casuali o tentativi ripetuti.
Guidava il ciclo di vita vulnerabile in modo deterministico.
Questo rendeva la causa principale e l'impatto molto più facili da sostenere.
Questo problema richiede influenza locale sul filesystem.
Un attaccante deve avere accesso sufficiente per creare, sostituire o ripuntare il symlink pertinente o un percorso collegato equivalente durante la finestra di vulnerabilità.
Il processo di Consul Template deve inoltre:
Questi requisiti sono importanti.
Non si trattava di una lettura arbitraria di file remota non autenticata in qualsiasi distribuzione predefinita.
Ma all'interno del modello di fiducia locale interessato, l'impatto era significativo:
sandbox_pathIl rischio aumenta quando il processo viene eseguito con accesso a credenziali sensibili, configurazione dei servizi, token o chiavi private.
HashiCorp ha classificato il problema come:
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N
Il punteggio base pubblicato è:
4.7 / Medium
Questo punteggio riflette i vincoli reali:
È una classificazione ragionevole.
Il problema è circoscritto nei prerequisiti per l'attaccante, ma il bypass della sandbox e la conseguente divulgazione di file sono entrambi concreti.
Il bollettino HashiCorp elenca:
Affected: consul-template up to 0.41.4
Fixed: consul-template 0.42.0
La correzione è stata distribuita nella release 0.42.0 il 15 aprile 2026.
La correzione era piccola e affrontava direttamente il legame rotto.
Invece di convalidare il target risolto e poi costruire la dipendenza dall'input grezzo, il codice corretto restituisce il percorso risolto e passa quel percorso a NewFileQuery():
resolvedPath, err := resolveSandboxedPath(
sandboxPath,
strings.TrimSpace(s),
)
if err != nil {
return "", err
}
d, err := dep.NewFileQuery(resolvedPath)
La proprietà di sicurezza è cambiata da:
a:
Questo chiude il percorso di ri-targeting del symlink segnalato, perché modificare il collegamento originale non cambia più il percorso memorizzato dalla dipendenza.
La patch ha anche aggiunto un test di regressione mirato che copre l'esatta sequenza:
È il tipo di remediation che si desidera per questo bug:
Ho segnalato questo problema privatamente a HashiCorp Security il 20 marzo 2026.
Il report includeva:
HashiCorp ha corretto il problema in 0.42.0 e ha pubblicato HCSEC-2026-12 il 12 maggio 2026.
IBM ha pubblicato un bollettino di sicurezza corrispondente per la stessa CVE.
Entrambi i bollettini ufficiali hanno riconosciuto il report come:
Mohamed Abdelaal (0xmrma)
La lezione chiave è semplice:
convalidare un percorso non è sufficiente se l'operazione successiva sul file può risolvere quel percorso verso un oggetto diverso
Questa regola vale ben oltre Consul Template.
Conta ovunque il codice esegua:
La proprietà di sicurezza più profonda non è:
"la stringa del percorso sembrava sicura una volta"
È:
l'oggetto utilizzato dall'operazione sensibile deve essere l'oggetto che ha superato la convalida
In questo caso, Consul Template ha convalidato una risoluzione e ne ha recuperata un'altra.
Quel divario è stato sufficiente.
sandbox_path era un confine di sicurezza esplicito per i file localipathInSandbox() risolveva e convalidava correttamente il target del symlinkFileQuery manteneva il percorso originale influenzato dall'attaccanteos.ReadFile0.42.0 ha corretto il bug vincolando la dipendenza al percorso risolto e convalidatoQuesta vulnerabilità non riguardava il bypass di un controllo sul prefisso di una stringa.
Riguardava il tempo e l'identità.
Consul Template controllava dove puntava il collegamento durante la valutazione del template. Il fetch della dipendenza successivamente poneva al filesystem la stessa domanda. Un attaccante poteva cambiare la risposta tra quei due momenti.
È per questo che è diventata CVE-2026-5061.
Corretto in consul-template 0.42.0.