Consul Template は、テンプレート評価時にシンボリックリンクが指す先を検証していましたが、その後の依存関係の取得では元のパスを読み取っていました。それらの操作の間にリンクを張り替えることで、サンドボックス内のファイル参照がサンドボックス外のファイル開示に変わってしまいました。
私は HashiCorp Consul Template をレビューしているときに、非常に具体的なセキュリティ上の疑問を抱いてこの問題を発見しました:
テンプレート評価中に sandbox_path がシンボリックリンクを検証した場合、その後のファイル読み取りは、その検証済みのターゲットに結び付けられたままになるのでしょうか?
このケースでは、答えは「いいえ」でした。
file テンプレートヘルパーは、テンプレート評価中にパスを解決し、設定されたサンドボックスを強制していました。しかし、そのチェックの後で、元の生のパスを使ってファイル依存関係を作成していました。
その依存関係は後で取得されました。
攻撃者が検証と依存関係の取得の間の隙にシンボリックリンクを張り替えた場合、Consul Template はサンドボックス外のファイルを読み取ることができました。次のレンダリングの前にリンクが復元されていれば、サンドボックス検証は再び通過し、キャッシュされた外部コンテンツが引き続きレンダリングされました。
この問題が CVE-2026-5061 になりました。
HashiCorp セキュリティ速報: HCSEC-2026-12
IBM セキュリティ速報: CVE-2026-5061 セキュリティ速報
CVE: CVE-2026-5061
修正バージョン: 0.42.0
photo0
攻撃者が制御するサンドボックス内シンボリックリンク -> サンドボックス検証が安全なターゲットに解決 -> 依存関係が元の生のパスを保存 -> 攻撃者がリンクをサンドボックス外に張り替え -> 依存関係の取得が外部ファイルを読み取り -> 攻撃者が安全なリンクを復元 -> 次の検証が通過 -> キャッシュされた外部コンテンツがレンダリングされる
Consul Template は、Consul と Vault のデータを扱うテンプレートレンダリングツールです。
継続的に実行して依存関係の変更を監視し、アプリケーションが利用するファイルや環境変数にデータをレンダリングできます。
file テンプレートヘルパーはローカルファイルを読み取り、その内容をレンダリング出力に挿入します。
ローカルファイルの読み取りはプロセスが読み取り可能な秘密情報を露出させる可能性があるため、Consul Template はこのヘルパーの境界として sandbox_path を提供しています。
文書化されたセキュリティ特性は単純明快です:
file に渡されるパスは、設定されたサンドボックス内にある必要があるこれにより sandbox_path は真のセキュリティ境界となります。
重要な疑問は、パスがサンドボックス配下にあるように見えるかどうかではありませんでした。
本当の疑問はこれでした:
読み取られるファイルは、サンドボックス検証を通過したファイルと同じままなのか?
脆弱なバージョンでは、そうではありませんでした。
ファイルシステムのサンドボックスは、パス検証とファイルアクセスの間の隙で失敗することがよくあります。
よくあるパターンは次のとおりです:
攻撃者が2つの操作の間にシンボリックリンクや同等のファイルシステムリダイレクションを変更できる場合、この想定は安全ではありません。
Consul Template では、テンプレート評価と依存関係の取得が別々の段階であったため、この攻撃面は特に興味深いものになりました。
その分離が、正しいセキュリティ上の疑問を生み出しました:
依存関係の取得は検証済みのターゲットに結び付けられているのか、それとも攻撃者が制御するパスを再び解決するのか?
それが私が注目した境界でした。
根本原因は、次の間でのチェック時と使用時の不一致(TOCTOU)でした:
template/funcs.go でのサンドボックス検証dependency/file.go での後続の依存関係読み取りテストしたリビジョンでは、fileFunc() は次の処理を行っていました:
normalized := strings.TrimSpace(s)
err := pathInSandbox(sandboxPath, normalized)
if err != nil {
return "", err
}
d, err := dep.NewFileQuery(s)
検証ヘルパーは、包含チェックの前にシンボリックリンクを正しく解決していました:
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)
}
つまり、サンドボックスチェック自体は解決済みのターゲットを認識していました。
しかし、その解決済みターゲットは破棄されていました。
NewFileQuery() は代わりに元の文字列を保存していました:
return &FileQuery{
stopCh: make(chan struct{}, 1),
path: s,
}, nil
その後、依存関係の取得がそのパスを再び読み取りました:
data, err := os.ReadFile(d.path)
これが脆弱性の全容です。
コードはあるファイルシステム解決をチェックし、その後で別の解決を使用していました。
シンボリックリンクは変更可能だからです。
攻撃者は、サンドボックス外のターゲットを pathInSandbox() に直接通過させる必要はありません。
検証後、Fetch() が読み取りを実行する前に、すでに承認された生のパスが解決する対象を変更するだけでよいのです。
その流れは次のとおりです:
pathInSandbox() の間、リンクは安全なファイルに解決されるFileQuery が生のリンクパスを記録するos.ReadFile(d.path) がリンクを再び解決する最後のステップが重要です。
安全なリンクを復元しても、すでに取得された秘密情報は依存関係キャッシュから削除されませんでした。
重要な違いは、明示的なセキュリティ制御のバイパスであることです。
これは単に次のような話ではありません:
「Consul Template が監視している間にファイルが変更された」
ファイルの変更を監視することは想定された動作です。
本当の問題は次のとおりでした:
文書化されたサンドボックス制限を通過したパスが、後でそのサンドボックス外のファイルを読み取るために使用される可能性がある
これは信頼境界の直接的な破綻です。
アプリケーションはすでにセキュリティ上の判断を下していました:
しかし、その後の取得は、その判断を正当化したターゲットに結び付けられていませんでした。
これこそが、ありふれたファイルシステムの可変性を脆弱性に変えたものです。
私は、実際の評価と依存関係取得のシーケンスを再現するスタンドアロンの再現プログラムを作成しました。
制御された環境では次のものを使用しました:
再現の流れは次のとおりでした:
file ヘルパーを評価し、サンドボックス検証を成功させ、生のパスを依存関係として登録します。os.ReadFile(d.path) がリダイレクトされたリンクを通して読み取れるようにします。観測された動作は次のとおりでした:
このハッシュ比較が重要でした。
最終的なレンダリング値が、古い安全なコンテンツでも、ファイル名の副産物でも、エラーパスの副作用でもないことを証明しました。
それはサンドボックス外のファイルのバイト単位で完全なコンテンツでした。
TOCTOU 問題の最も強力な証明は、タイムラインを制御しなければなりません。
シンボリックリンクがサンドボックス外を指せることを示すだけでは弱いでしょう。pathInSandbox() は外部ターゲットを観測した時点でそれをすでに拒否していたからです。
セキュリティ上の主張は、次のすべての状態を順番に証明することに依存していました:
だからこそ、再現プログラムはテンプレート検証、依存関係の取得、リンクの復元、次のレンダリングを明示的に分離しました。
PoC はランダムなタイミングや繰り返しの推測に依存していませんでした。
脆弱なライフサイクルを決定的に進行させました。
これにより、根本原因と影響をはるかに立証しやすくなりました。
この問題には、ローカルファイルシステムへの操作権限が必要です。
攻撃者は、脆弱な時間帯に該当するシンボリックリンクや同等のリンクパスを作成、置換、または張り替えるために十分なアクセス権が必要です。
さらに、Consul Template プロセスが次の条件を満たす必要があります:
これらの要件は重要です。
これは、デフォルトのデプロイ環境における未認証のリモート任意ファイル読み取りではありませんでした。
しかし、影響を受けるローカルの信頼モデル内では、影響は無視できないものでした:
sandbox_path 制限のバイパスプロセスが機密の認証情報、サービス設定、トークン、秘密鍵にアクセスできる状態で実行されている場合、リスクは高まります。
HashiCorp はこの問題を次のように分類しました:
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N
公開されている基本スコアは次のとおりです:
4.7 / Medium
このスコアは実際の制約を反映しています:
これは合理的な分類です。
この問題は攻撃者の前提条件が限定的ですが、サンドボックスのバイパスとそれに伴うファイル開示はどちらも現実的です。
HashiCorp の速報には次のように記載されています:
Affected: consul-template up to 0.41.4
Fixed: consul-template 0.42.0
修正は 2026年4月15日 の 0.42.0 リリースで提供されました。
修正は小さく、壊れた結合に直接対処しました。
パッチ適用後のコードは、解決済みターゲットを検証してから生の入力から依存関係を構築する代わりに、解決済みパスを返し、そのパスを NewFileQuery() に渡します:
resolvedPath, err := resolveSandboxedPath(
sandboxPath,
strings.TrimSpace(s),
)
if err != nil {
return "", err
}
d, err := dep.NewFileQuery(resolvedPath)
セキュリティ特性は次の状態から変更されました:
次の状態へ:
これにより、報告されたシンボリックリンクの張り替え経路は封鎖されます。元のリンクを変更しても、依存関係が保存するパスは変わらないからです。
さらに、パッチは次の正確なシーケンスを対象とした回帰テストも追加しました:
これは、このバグに対して望ましい修正の形です:
私は 2026年3月20日 にこの問題を HashiCorp Security に非公開で報告しました。
報告には次のものが含まれていました:
HashiCorp は 0.42.0 で問題を修正し、2026年5月12日 に HCSEC-2026-12 を公開しました。
IBM は同じ CVE に対応するセキュリティ速報を公開しました。
両方の公式速報は、報告者を次のように記載しています:
Mohamed Abdelaal (0xmrma)
重要な教訓は単純です:
後のファイル操作がそのパスを別のオブジェクトに解決できる場合、パスを検証するだけでは不十分である
この原則は Consul Template にとどまりません。
コードが次のような処理を行う場所ならどこでも重要です:
より深いセキュリティ特性は次のようなものではありません:
「パス文字列が一度は安全に見えた」
次のようなものです:
機密操作で使用されるオブジェクトは、検証を通過したオブジェクトでなければならない
このケースでは、Consul Template はある解決を検証し、別の解決を取得しました。
その隙だけで十分でした。
sandbox_path は明示的なローカルファイルのセキュリティ境界であったpathInSandbox() はシンボリックリンクのターゲットを正しく解決し、検証したFileQuery は攻撃者の影響を受けた元のパスを保持したos.ReadFile を通じてそのパスを再び解決した0.42.0 は依存関係を解決済み・検証済みのパスに結び付けることでバグを修正したこの脆弱性は、文字列プレフィックスのチェックをバイパスする話ではありませんでした。
それは時間と同一性の問題でした。
Consul Template はテンプレート評価時にリンクがどこを指しているかをチェックしました。 依存関係の取得は、後にファイルシステムに同じ質問を再び投げかけました。 攻撃者はその2つの瞬間の間に答えを変えることができました。
それが、これが CVE-2026-5061 になった理由です。
consul-template 0.42.0 で修正されました。