コンテナセキュリティの幻想:runcブレイクアウト(CVE-2019-5736)の深層と、Falcoによるリアルタイム検知の極意
「コンテナは仮想マシンではない。ただのプロセスだ」
この事実を何度言い聞かせても、いまだに「コンテナに入ったから安全」「名前空間(Namespace)とcgroupで完全に隔離されている」と誤認しているエンジニアが後を絶たない。
我々オフセッシブ・セキュリティの文脈において、コンテナの分離境界など、適切なカーネルパラメータのチューニングがされていないデフォルト設定であれば、紙細工の城に等しい。その象徴たる脆弱性が、2019年早々に世界を震撼させた CVE-2019-5736(runcのコンテナ脱獄脆弱性)である。
今回は、この CVE-2019-5736 が突きつける低レイヤのメモリ挙動の闇と、ホストを護るためのFalcoを用いたランタイムセキュリティの極意を、現場の泥臭いインシデントハンドリングの視点から紐解いていこう。
—
1. 根本原因の解析:なぜ runc は乗っ取られたのか
CVE-2019-5736 の本質は、コンテナランタイム(runc)がホスト上のバイナリを実行する際の「ファイルディスクリプタ(FD)の扱い方」および「 /proc/self/exe の参照メカニズム」の致命的な欠陥にある。
低レイヤのメモリ挙動とファイルデスクリプタの罠
攻撃者がどのようにしてホストを制圧するのか、そのキルチェーンの低レイヤの挙動を追う。
1. 悪意あるイメージの実行: 攻撃者は、内部で特定の細工をしたバイナリを実行するコンテナを起動させる。
2. runc バイナリへの上書き試行: コンテナ内からホスト側の runc バイナリ(あるいは docker-runc)に対して書き込みを行おうとする。通常、稼働中のバイナリファイルへの書き込みはLinuxカーネルによって ETXTBSY(Text file busy)エラーが返され、ブロックされる。
3. /proc/self/exe のマジック: ここに盲点がある。コンテナの初期化プロセスにおいて、runc は自身を指すファイルディスクリプタを保持したまま、コンテナ内のプロセスを実行する。コンテナ内から /proc/self/exe を通じてホスト上の runc バイナリそのものを「読み込み専用」でオープンすることが可能になってしまうのだ。
4. O_TRUNC による競合(Race Condition): 攻撃者は、ホスト側で runc が再度実行される瞬間(例えば docker exec が叩かれた時など)を狙い、/proc/self/exe 経由で開いていたファイルデスクリプタに対して、強烈なタイミング攻撃(Race Condition)を仕掛ける。runc が自身のバイナリを再実行しようとディスクから読み込むその刹那、攻撃者のプロセスがファイルハンドルに対して書き込み(O_TRUNC)を成功させると、ホスト上の runc バイナリの中身が攻撃者のペイロードに置き換わる。
結果として、次にホスト上で管理者やオーケストレータが docker exec や runc コマンドを実行した瞬間、ホストのroot権限で攻撃者のコードが実行される。これがコンテナ脱獄(ブレイクアウト)のメカニズムだ。
—
2. パッチと防御の限界:なぜランタイム保護が必要なのか
CVE-2019-5736 に対するパッチは、ファイルディスクリプタをメモリ上にコピーし、ファイルシステム上のファイルパスを直接参照させないようにすることで修正された。しかし、セキュリティアーキテクトとして知っておくべきは、「脆弱性は塞がれても、コンテナ脱獄の手法はこれに留まらない」という残酷な現実である。
カーネルのエクスプロイト、不適切なケーパビリティ(Capabilities)の付与、あるいは過剰に特権を与えた privileged コンテナの存在により、攻撃者は常にホストへの侵食を狙っている。
ここで、静的な脆弱性スキャナー(Image Scanner)だけでは無力であることが露呈する。どれほどビルド時のイメージをクリーンに保とうとも、ゼロデイや設定ミスによるランタイムでの挙動変化は防げない。だからこそ、ランタイムのシステムコールを監視し、異常をリアルタイムで検知・遮断する仕組み(Runtime Security)が不可欠となるのだ。
—
3. Falcoによる異常なシステムコール実行の検知
クラウドネイティブ・セキュリティのデファクトスタンダードである Falco は、Linuxカーネルモジュール(あるいはeBPF)を活用し、システムコールレベルでプロセスの挙動を監視する。
runc ブレイクアウトの兆候や、コンテナ内からの不審なホストリソースへのアクセスを検知するための、実用的なFalcoルールの設定例を提示する。
実用的なFalcoカスタムルール設定例
以下の設定(/etc/falco/rules.local.yaml 等に配置)は、コンテナ内からの予期せぬバイナリ書き込みや、特権昇格の兆候を検知するためのカスタムルールである。
- rule: Detect Container Escape Attempt via Binary Tampering
desc: コンテナ内からホスト側の重要バイナリまたはランタイムへの書き込み試行を検知する
condition: >
container and
evt.type in (open, openat, creat) and
(fd.name startswith /usr/bin/ or fd.name startswith /usr/sbin/ or fd.name startswith /usr/local/bin/) and
(evt.is_open_write = true)
output: >
潜在的なコンテナ脱獄またはファイル改ざんの検知
(user=%user.name command=%proc.cmdline file=%fd.name container_id=%container.id container_name=%container.name)
priority: CRITICAL
tags: [container, attack, filesystem]
- rule: Unexpected Privileged Escalation inside Container
desc: コンテナ内でroot以外のユーザーから予期せぬ特権昇格(setuid/setgid等)が発生したことを検知する
condition: >
container and
evt.type in (setuid, setreuid, setresuid) and
user.uid != 0 and
proc.suid = 0
output: >
コンテナ内での不正な特権昇格を検知
(user=%user.name target_uid=%user.uid parent_proc=%proc.pname command=%proc.cmdline container_id=%container.id)
priority: ERROR
tags: [container, privilege_escalation]
この設定のポイント
evt.type in (open, openat, creat): ファイルオープンや作成に関わるシステムコールをフックする。fd.name startswith: ホスト系(あるいはコンテナ内のマウントされた領域を介した)のバイナリパスへの書き込みをピンポイントで捉える。- eBPFバックエンドの活用: 従来のカーネルモジュール方式ではなく、近年のLinuxカーネルでは
eBPFをドライバとして指定することで、カーネルクラッシュのリスクを最小限に抑えつつ、高速なイベントキャプチャを実現すべきである。
—
4. 現場のインシデントハンドリングとハードニングの鉄則
もし、Falcoが上述の CRITICAL アラートを検知した場合、現場のエンジニアはどのような手順で初動対応(Incident Response)を行うべきか。実務に直結するフローを記す。
1. コンテナの即時隔離(不審なプロセスの凍結):
ネットワークを切断することなく、まずはプロセスの実行を停止(SIGSTOP)させる。
# 該当するコンテナのプロセスID(PID)を特定し、一時停止させる
kill -STOP <PID>
2. フォレンジック用のメモリ・ディスク保全:
コンテナを強制削除(docker rm -f)してはならない。痕跡が消滅する。オーバーレイファイルシステム(OverlayFS)のマウントポイントから、書き換えられた可能性のあるファイルを保全する。
3. ランタイムの完全性検証:
ホスト側の runc や containerd のバイナリハッシュ(SHA-256)を、パッケージマネージャの記録または信頼されたベースラインと比較検証する。
ハードニングの最終防衛線
CVE-2019-5736 のようなランタイムの脆弱性や、将来現れる未知のゼロデイからインフラを守るためには、以下の要塞化を徹底することだ。
- Rootlessモードの全面採用: デフォルトの
rootユーザーでコンテナランタイムを動かす文化を今すぐ捨て去るべきだ。Rootless DockerやPodmanを導入し、万が一コンテナが脱獄に成功したとしても、それはホスト上の非特権ユーザー空間に留まるように設計を強制する。 - Seccompプロファイルの厳格化: コンテナが実行できるシステムコールを必要最小限にホワイトリスト方式で制限する。DockerデフォルトのSeccompプロファイルに加え、アプリケーション特性に応じたカスタムプロファイル(AppArmorやSELinuxのポリシーと併用)を適用する。
セキュリティとは、単一の壁の堅牢さではなく、多層防御(Defense in Depth)の厚みで決まる。コンテナの利便性に溺れ、足元の土壌(カーネルとランタイム)を見失うことなかれ。あなたのインフラは、本当に「隔離」されているか?今夜、稼働中のクラスタのFalcoログを確認することを強く勧める。
コメント