
O Consul Template validava para onde o symlink apontava durante a avaliação do template, mas sua busca de dependência posterior lia o caminho original. Redirecionar o link entre essas operações transformou uma referência de arquivo dentro do sandbox em uma divulgação de arquivo fora do sandbox.
O Consul Template validava para onde um symlink apontava durante a avaliação do template, mas a busca de dependência posterior lia o caminho original. Redirecionar o link entre essas operações transformava uma referência de arquivo dentro do sandbox em uma divulgação de arquivo fora do sandbox.
Encontrei este problema enquanto revisava o HashiCorp Consul Template, com uma questão de segurança muito específica em mente:
Se o sandbox_path valida um symlink durante a avaliação do template, a leitura de arquivo posterior permanece vinculada ao mesmo alvo validado?
Neste caso, a resposta foi não.
O helper de template file resolvia o caminho e aplicava o sandbox configurado durante a avaliação do template. Mas, após essa verificação, ele criava uma dependência de arquivo usando o caminho bruto original.
Essa dependência era buscada posteriormente.
Se um atacante redirecionasse o symlink na lacuna entre a validação e a busca da dependência, o Consul Template poderia ler um arquivo fora do sandbox. Se o link fosse restaurado antes da próxima renderização, a validação do sandbox passava novamente e o conteúdo externo em cache ainda era renderizado.
Esse problema tornou-se o CVE-2026-5061.
Boletim da HashiCorp: HCSEC-2026-12
Boletim da IBM: boletim de segurança CVE-2026-5061
0.42.0photo0
attacker-controlled in-sandbox symlink -> sandbox validation resolves to safe target -> dependency stores original raw path -> attacker retargets link outside sandbox -> dependency fetch reads external file -> attacker restores safe link -> next validation passes -> cached external content is rendered
Consul Template é uma ferramenta de renderização de templates para dados do Consul e do Vault.
Ele pode ser executado continuamente, monitorar dependências em busca de alterações e renderizar dados em arquivos ou variáveis de ambiente para que os aplicativos consumam.
O helper de template file lê um arquivo local e insere seu conteúdo na saída renderizada.
Como as leituras de arquivos locais podem expor segredos legíveis pelo processo, o Consul Template fornece o sandbox_path como um limite para esse helper.
A propriedade de segurança documentada é direta:
file devem estar dentro do sandbox configuradoIsso torna o sandbox_path um limite de segurança real.
A questão importante não era se o caminho parecia estar sob o sandbox.
A verdadeira questão era:
O arquivo que é lido permanece sendo o mesmo arquivo que passou pela validação do sandbox?
Nas versões vulneráveis, não permanecia.
Sandboxes de sistema de arquivos frequentemente falham na lacuna entre a validação do caminho e o acesso ao arquivo.
O padrão comum é:
Essa suposição é insegura quando um atacante pode modificar um symlink ou um redirecionamento equivalente do sistema de arquivos entre as duas operações.
O Consul Template tornou essa superfície especialmente interessante porque a avaliação do template e a busca de dependências eram estágios separados.
Essa separação criou a questão de segurança certa:
A busca da dependência está vinculada ao alvo validado, ou ela resolve novamente o caminho controlado pelo atacante?
Era esse o limite no qual eu me concentrei.
A causa raiz era uma incompatibilidade de tempo-de-verificação para tempo-de-uso entre:
template/funcs.godependency/file.goNa revisão testada, o fileFunc() fazia o seguinte:
normalized := strings.TrimSpace(s)
err := pathInSandbox(sandboxPath, normalized)
if err != nil {
return "", err
}
d, err := dep.NewFileQuery(s)
O helper de validação resolvia corretamente os symlinks antes de verificar a contenção:
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)
}
Portanto, a própria verificação do sandbox entendia o alvo resolvido.
Mas esse alvo resolvido era descartado.
O NewFileQuery() armazenava a string original em vez disso:
return &FileQuery{
stopCh: make(chan struct{}, 1),
path: s,
}, nil
Então, a busca posterior da dependência lia esse caminho novamente:
data, err := os.ReadFile(d.path)
Essa é a vulnerabilidade completa.
O código verificava uma resolução do sistema de arquivos e, depois, usava outra.
Porque um symlink é mutável.
O atacante não precisa fazer um alvo fora do sandbox passar pelo pathInSandbox() diretamente.
O atacante só precisa mudar para onde o caminho bruto já aprovado resolve, após a validação, mas antes que o Fetch() realize a leitura.
A sequência é:
pathInSandbox()FileQuery registra o caminho bruto do linkos.ReadFile(d.path) resolve o link novamenteEsse último passo é importante.
Restaurar o link seguro não removia o segredo já buscado do cache de dependências.
A distinção importante é a bypass de um controle de segurança explícito.
Isso não era meramente:
"o arquivo mudou enquanto o Consul Template o monitorava"
Monitorar arquivos em busca de alterações é um comportamento esperado.
O problema real era:
um caminho que passou pela restrição de sandbox documentada poderia, posteriormente, ser usado para ler um arquivo fora desse sandbox
Isso é uma falha direta do limite de confiança.
O aplicativo já havia tomado uma decisão de segurança:
Mas a busca posterior não estava vinculada ao alvo que justificava essa decisão.
Foi isso que transformou a mutabilidade comum do sistema de arquivos em uma vulnerabilidade.
Construí um reprodutor independente em torno da sequência exata de avaliação e busca de dependência.
A configuração controlada usava:
O fluxo de reprodução era:
file para que a validação do sandbox seja bem-sucedida e o caminho bruto seja registrado como uma dependência.os.ReadFile(d.path) leia através do link redirecionado.O comportamento observado era:
Essa comparação de hash era importante.
Ela provou que o valor final renderizado não era conteúdo seguro obsoleto, um artefato de nome de arquivo ou um efeito colateral do caminho de erro.
Era o conteúdo byte a byte exato do arquivo fora do sandbox.
A prova mais forte para um problema TOCTOU deve controlar a linha do tempo.
Simplesmente mostrar que um symlink pode apontar para fora do sandbox seria mais fraco, porque o pathInSandbox() já rejeitava um alvo externo quando o observava.
A alegação de segurança dependia de provar todos esses estados em ordem:
Foi por isso que o reprodutor separou explicitamente a validação do template, a busca da dependência, a restauração do link e a próxima renderização.
A PoC não dependia de timing aleatório ou de tentativas repetidas.
Ela conduziu o ciclo de vida vulnerável de forma determinística.
Isso tornou a causa raiz e o impacto muito mais fáceis de defender.
Este problema exige influência local sobre o sistema de arquivos.
Um atacante precisa de acesso suficiente para criar, substituir ou redirecionar o symlink relevante ou um caminho vinculado equivalente durante a janela vulnerável.
O processo do Consul Template também deve:
Esses requisitos são importantes.
Isso não era uma leitura arbitrária de arquivo remota não autenticada em qualquer implantação padrão.
Mas, dentro do modelo de confiança local afetado, o impacto era significativo:
sandbox_pathO risco aumenta quando o processo é executado com acesso a credenciais sensíveis, configuração de serviços, tokens ou chaves privadas.
A HashiCorp classificou o problema como:
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N
A pontuação base publicada é:
4.7 / Medium
Essa pontuação reflete as restrições reais:
Essa é uma classificação razoável.
O problema é restrito em pré-requisitos do atacante, mas a bypass do sandbox e a divulgação de arquivo resultante são ambas concretas.
O boletim da HashiCorp lista:
Affected: consul-template up to 0.41.4
Fixed: consul-template 0.42.0
A correção foi lançada na versão 0.42.0 em 15 de abril de 2026.
A correção foi pequena e abordou diretamente o vínculo quebrado.
Em vez de validar o alvo resolvido e depois construir a dependência a partir da entrada bruta, o código corrigido retorna o caminho resolvido e entrega esse caminho ao NewFileQuery():
resolvedPath, err := resolveSandboxedPath(
sandboxPath,
strings.TrimSpace(s),
)
if err != nil {
return "", err
}
d, err := dep.NewFileQuery(resolvedPath)
A propriedade de segurança mudou de:
para:
Isso encerra o caminho de redirecionamento de symlink relatado, porque alterar o link original não muda mais o caminho armazenado pela dependência.
O patch também adicionou um teste de regressão focado cobrindo a sequência exata:
Esse é o tipo de remediação que você deseja para esse bug:
Informei este problema de forma privada à HashiCorp Security em 20 de março de 2026.
O relatório incluía:
A HashiCorp corrigiu o problema na versão 0.42.0 e publicou o HCSEC-2026-12 em 12 de maio de 2026.
A IBM publicou um boletim de segurança correspondente para o mesmo CVE.
Ambos os boletins oficiais creditaram o relatório a:
Mohamed Abdelaal (0xmrma)
A lição principal é simples:
validar um caminho não é suficiente se a operação de arquivo posterior puder resolver esse caminho para um objeto diferente
Essa regra se aplica muito além do Consul Template.
Isso importa em qualquer lugar onde o código faça:
A propriedade de segurança mais profunda não é:
"a string do caminho pareceu segura uma vez"
É:
o objeto usado pela operação sensível deve ser o objeto que passou pela validação
Neste caso, o Consul Template validou uma resolução e buscou outra.
Essa lacuna foi suficiente.
sandbox_path era um limite de segurança explícito para arquivos locaispathInSandbox() resolvia e validava o alvo do symlink corretamenteFileQuery mantinha o caminho original influenciado pelo atacanteos.ReadFile0.42.0 corrigiu o bug vinculando a dependência ao caminho resolvido e validadoEsta vulnerabilidade não era sobre contornar uma verificação de prefixo de string.
Era sobre tempo e identidade.
O Consul Template verificava para onde o link apontava durante a avaliação do template.
A busca da dependência fazia a mesma pergunta ao sistema de arquivos novamente, mais tarde.
Um atacante podia mudar a resposta entre esses dois momentos.
Foi por isso que este problema se tornou o CVE-2026-5061.
Corrigido no consul-template 0.42.0.