
O helper writeToFile do Consul Template abria diretamente um destino fornecido pelo operador e seguia componentes de caminho com link, permitindo que a saída renderizada escapasse do diretório pretendido e sobrescrevesse um arquivo pré-existente.
O helper writeToFile do Consul Template abria diretamente um destino fornecido pelo operador e seguia componentes de caminho vinculados, permitindo que a saída renderizada escapasse do diretório pretendido e sobrescrevesse um arquivo preexistente.
Encontrei este problema ao revisar o HashiCorp Consul Template, com uma pergunta direta de segurança de filesystem em mente:
Se writeToFile receber um caminho que parece estar dentro do diretório pretendido, ele verifica onde o filesystem realmente gravará os dados?
Neste caso, a resposta foi não.
O helper de template writeToFile abria o caminho final fornecido pelo usuário diretamente por meio de os.Create() ou os.OpenFile().
Essas operações seguiam links simbólicos, junções de diretório e redirecionamentos equivalentes de filesystem já presentes no caminho de destino.
Isso significava que a string de caminho podia permanecer sob a raiz pretendida pelo operador enquanto a gravação real ocorria em outro lugar.
Na minha prova de conceito controlada, um diretório pai vinculado redirecionou a saída renderizada para fora da árvore pretendida e fez com que um arquivo de destino preexistente fosse sobrescrito.
Esse problema tornou-se CVE-2026-14361.
Boletim da HashiCorp:
0.42.1photo0
o destino fornecido pelo operador parece estar dentro da raiz pretendida -> o atacante pré-posiciona o componente pai ou final vinculado -> writeToFile abre o caminho diretamente -> o filesystem resolve a gravação fora do diretório pretendido -> o segredo renderizado é redirecionado -> o destino preexistente pode ser sobrescrito
O Consul Template renderiza dados de fontes como Consul e Vault.
O helper writeToFile permite que um template grave conteúdo selecionado em um arquivo local separado, aplicando um proprietário, grupo e modo de permissão solicitados.
A documentação da HashiCorp demonstra especificamente o helper com material de 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
Isso faz dele mais do que um helper de saída comum.
O conteúdo que cruza esse limite pode incluir:
A pergunta importante não era se o writeToFile poderia criar um nome de arquivo solicitado.
A pergunta real era:
O processo grava no local de filesystem pretendido pelo operador, ou meramente em qualquer objeto para o qual o caminho resolva no momento da abertura?
Em versões vulneráveis, ele confiava na segunda opção.
Helpers de gravação são superfícies de segurança de alto valor porque fazem a transição de dados de aplicação para mutação do filesystem.
As falhas interessantes geralmente não são a travessia clássica de ../.
São falhas de resolução:
Isso é especialmente importante quando o processo executa com mais privilégios de filesystem do que o atacante.
Um atacante local com baixos privilégios pode não conseguir sobrescrever diretamente um arquivo sensível.
Mas se ele puder influenciar um componente de caminho sob um diretório de gravação pretendido, um processo do Consul Template com mais privilégios pode realizar a gravação por ele.
Essa foi a fronteira na qual me concentrei.
Não abordei isso como uma revisão genérica de travessia de caminho.
O caminho fornecido não precisava de segmentos ...
Ele podia permanecer lexicalmente dentro da raiz pretendida durante todo o tempo.
A pergunta mais forte era:
Os componentes de destino vinculados são rejeitados antes que o conteúdo sensível seja gravado?
Essa pergunta importa para ambos os casos:
O segundo caso é especialmente útil porque logs e configuração ainda mostram um caminho de aparência inofensiva sob o diretório esperado.
O filesystem o resolve em outro lugar.
A causa raiz era a criação direta de arquivos baseada em caminho, sem validação ciente de links.
Na revisão testada, writeToFile() selecionava um de dois caminhos de abertura.
O modo de anexação usava:
f, err = os.OpenFile(
path,
os.O_APPEND|os.O_WRONLY|os.O_CREATE,
perm,
)
O modo de gravação normal usava:
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)
Nenhum dos dois caminhos rejeitava componentes de destino vinculados antes que o arquivo fosse aberto.
Isso importa porque:
os.Create(path) segue redirecionamentos de filesystem existentes e trunca o arquivo resolvidoos.OpenFile(path, ...) segue componentes de caminho vinculados durante o modo de anexaçãoA mesma suposição baseada em caminho continuava após a gravação.
Proprietário e permissões eram aplicados usando o caminho novamente:
err = os.Chown(path, uid, gid)
err = os.Chmod(path, perm)
Isso significava que as operações de metadados também estavam vinculadas a um nome de caminho mutável, em vez do descritor de arquivo já aberto.
Porque o atacante pode preparar o redirecionamento antes que o writeToFile execute.
Nenhuma condição de corrida (race) probabilística é necessária para o ataque básico.
A sequência é direta:
writeToFile o abre diretamenteEssa é a vulnerabilidade por completo.
É verdade que sistemas operacionais normalmente seguem symlinks ao abrir arquivos por caminho.
Isso não torna esse comportamento de aplicação seguro.
A pergunta de segurança não é:
"O Go se comportou como documentado?"
A pergunta real é:
Um helper que grava dados sensíveis renderizados verificou se o destino resolvido correspondia ao local pretendido pelo operador?
Em versões vulneráveis, não verificou.
Essa distinção importa porque o Consul Template pode executar:
Seguir redirecionamentos de filesystem posicionados pelo atacante nesse contexto cria um problema real de privilégio e de fronteira de confiança.
Construí um reprodutor independente em torno do comportamento exato do writeToFile no commit testado.
A configuração controlada usava:
writeToFileO fluxo de reprodução era:
O comportamento observado foi:
writeToFile seguiu o redirecionamentoIsso estabeleceu ambas as partes da alegação:
Um symlink no componente final já demonstraria o seguimento inseguro de links.
Mas um componente pai vinculado prova um ponto operacional mais forte:
O destino preexistente também importava.
Sem ele, o PoC mostraria apenas criação inesperada de arquivos.
Ao começar com um arquivo existente e verificar seu hash final, a reprodução provou diretamente o comportamento de sobrescrita.
Isso tornou o resultado mais concreto do que uma alegação baseada apenas no código-fonte.
Este problema exige influência local sobre o filesystem.
Um atacante precisa de acesso suficiente para criar ou modificar um symlink, uma junção de diretório ou um redirecionamento equivalente dentro ou abaixo do local de gravação pretendido.
O processo do Consul Template deve então gravar através desse caminho.
O impacto depende fortemente dos privilégios do processo e do conteúdo renderizado.
Os resultados práticos incluem:
Isso não era uma gravação arbitrária remota não autenticada a partir de uma instalação padrão.
Mas era uma clara falha local de fronteira de confiança, com impacto significativo em implantações privilegiadas ou com filesystem compartilhado.
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
Esse vetor reflete:
Os boletins oficiais também documentam explicitamente que a gravação redirecionada pode sobrescrever um arquivo preexistente.
A pontuação é moderada porque a exploração depende de condições locais de controle de caminho, não porque o efeito no filesystem seja teórico.
O boletim da HashiCorp lista:
Affected: consul-template up to and including 0.42.0
Fixed: consul-template 0.42.1
A correção foi lançada na versão 0.42.1 em 8 de julho de 2026.
O patch 0.42.1 endureceu o caminho de gravação em várias camadas.
O helper corrigido usa os.Lstat() para inspecionar:
e rejeita esses componentes quando são links.
Isso encerra os casos de redirecionamento direto pelo diretório pai e de symlink no arquivo final cobertos pelo relatório e pelos testes de regressão.
Em plataformas Unix com suporte, o destino é aberto com O_NOFOLLOW.
Isso faz a própria abertura falhar se o componente final se tornar um symlink entre a pré-verificação e a abertura.
Isso é importante porque uma pré-verificação isolada pode criar outra janela de TOCTOU.
A implementação específica de plataforma é um no-op no Windows, onde O_NOFOLLOW não está disponível pelo mesmo mecanismo.
O patch substituiu as operações de proprietário e modo baseadas em caminho por operações baseadas no descritor:
f.Chown(uid, gid)
f.Chmod(perm)
Isso vincula as alterações de metadados ao arquivo que foi realmente aberto, em vez de resolver o caminho novamente mais tarde.
O helper agora cria o diretório pai somente quando a falha de stat é genuinamente os.IsNotExist.
Outros erros, como falhas de permissão, são retornados em vez de continuar no caminho de gravação.
O patch adicionou testes focados para:
A discussão pública do patch documenta uma limitação importante com clareza.
A nova validação verifica:
Ela não percorre e rejeita todos os componentes ancestrais superiores.
Os mantenedores escolheram esse limite porque o writeToFile não tem uma raiz de sandbox configurada para ancorar uma verificação completa de contenção, e sistemas operacionais comuns podem incluir links gerenciados legítimos em prefixos de caminho, como /var -> /private/var no macOS.
O_NOFOLLOW também protege o componente final, não todos os diretórios ancestrais.
Isso não altera o status da vulnerabilidade reportada nem a versão corrigida oficial.
Mas esclarece a propriedade de segurança exata entregue pelo patch:
Essa distinção vale a pena preservar em um writeup técnico.
Reportei este problema em particular à HashiCorp Security em 21 de março de 2026, como uma segunda descoberta independente no Consul Template.
O relatório incluía:
O e-mail de acompanhamento original não estava presente na fila de relatórios da equipe de segurança, provavelmente por ter sido capturado por um filtro de spam da lista de discussão.
Depois que reencaminhei o relatório completo, a HashiCorp contatou a equipe de engenharia e investigou o problema separadamente da primeira questão do Consul Template.
A HashiCorp corrigiu a vulnerabilidade em 0.42.1 e publicou o HCSEC-2026-20 em 8 de julho 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:
uma string de caminho não é a mesma coisa que o objeto de filesystem que ela nomeia
Essa distinção importa sempre que código privilegiado grava caminhos influenciados pelo atacante.
Verificar se uma string começa com um diretório pretendido não é suficiente.
Até um caminho limpo, sem sequências de travessia, pode resolver para outro lugar através de:
A operação sensível deve ser vinculada a um destino cuja identidade tenha sido validada na fronteira correta.
Este problema também reforça uma regra mais ampla:
quando você já tem um descritor de arquivo aberto, aplique operações sensíveis à segurança através desse descritor, em vez de resolver o caminho novamente
É exatamente por isso que as alterações de Chown e Chmod baseadas no descritor importam.
writeToFile é documentado para material sensível, como certificados e chaves privadasos.Create ou os.OpenFileChown e Chmod baseados em caminho introduziam confiança adicional em caminhos mutáveis0.42.1 adicionou verificações de links, O_NOFOLLOW onde houver suporte, operações de metadados baseadas no descritor e testes de regressãoEsta vulnerabilidade não era sobre travessia de ../.
A string de caminho parecia correta.
O destino no filesystem não era.
O Consul Template aceitou o caminho pretendido pelo operador, seguiu um redirecionamento posicionado pelo atacante e gravou o conteúdo renderizado em um arquivo diferente.
É por isso que isso se tornou CVE-2026-14361.
Corrigido em consul-template 0.42.1.