コンテナの「壁」は幻か?runc脱獄の脅威と、ランタイム防御の最前線
「コンテナはプロセスを隔離しているから安全だ」。もし君がそう信じているなら、今すぐその認識を改めたほうがいい。
かつて我々を震撼させた CVE-2019-5736 を覚えているだろうか?これはコンテナランタイム runc の脆弱性であり、攻撃者がコンテナ内の権限からホストOSのバイナリを書き換えることで、いとも簡単に「脱獄(Container Escape)」を成功させるものだった。
多くのエンジニアは「パッチを当てれば終わり」と考えがちだが、セキュリティの本質はそこではない。コンテナはあくまで「共有カーネル」の上で動くプロセスに過ぎない。パッチが適用されていないゼロデイや、設定不備を突かれた瞬間に、その境界線は霧のように消え去るんだ。
今日は、コンテナ脱獄の深淵を覗きつつ、現場で泥臭く生き残るための「ランタイム防御」の極意を伝授しよう。
—
1. 攻撃者が狙う「盲点」:なぜコンテナは脱獄されるのか
CVE-2019-5736 の本質は、コンテナ内のプロセスがホスト上の runc バイナリへファイルディスクリプタを介してアクセスし、上書きできてしまう点にある。
攻撃者は、コンテナが実行する実行ファイルをすり替えることで、ホスト側でroot権限を得る。一度ホストを掌握すれば、他のコンテナの中身を覗くことも、クラウドのメタデータAPIを叩いてIAMロールを奪うことも可能だ。
君たちが守るべきは、単なる「パッチ管理」ではない。「コンテナが本来行うはずのない不審な動きを、カーネルレベルで即座に遮断する仕組み」だ。
—
2. Falcoによる「異常なシステムコール」の検知
静的なセキュリティチェック(イメージスキャンなど)は「出発前の検問」に過ぎない。走行中の車(稼働中のコンテナ)で起きる異常を検知するには、Falco のようなランタイムセキュリティツールが不可欠だ。
Falcoはカーネルのシステムコールを監視する。「コンテナ内で誰かが execve を叩いていないか?」「突然 /etc/shadow を読み込もうとしていないか?」といった異常を、リアルタイムで検知し、即座にSlackやSIEMへアラートを飛ばす。
実践:Falcoのカスタムルール設定例
以下のルールは、コンテナ内での予期せぬシェル実行やファイル書き込みを厳格に検知するためのものだ。
# falco_rules.local.yaml
- rule: Unexpected shell in container
desc: コンテナ内でシェルが実行されたことを検知
condition: >
spawned_process and container and
proc.name in (sh, bash, zsh, dash) and
container.id != host
output: "警告: コンテナ内で不審なシェルが起動されました (user=%user.name container_id=%container.id command=%proc.cmdline)"
priority: CRITICAL
- rule: Sensitive file modification in container
desc: コンテナからホストの機密ファイルへ書き込みを試行
condition: >
open_write and container and
fd.name startswith /etc/
output: "緊急: コンテナがホストの機密ファイルへ書き込みを試行しました (file=%fd.name)"
priority: EMERGENCY
—
3. 防御の要:読み取り専用ファイルシステムと権限の最小化
どれほど検知ツールが優秀でも、そもそも「穴」を塞いでおくことが鉄則だ。コンテナの要塞化(ハーデニング)において、以下の設定は妥協してはならない。
Dockerfileでのセキュリティ強化サンプル
コンテナを root ユーザーで動かすのは、泥棒に鍵を渡して家に入れてあげるようなものだ。
# 不要なツールを削除し、最小限のイメージにする
FROM alpine:3.18
RUN apk update && apk upgrade && \
apk del curl wget # 攻撃に使われやすいツールを削除
# 実行ユーザーをrootから変更
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# ファイルシステムを読み取り専用にする(アプリケーションが書き込みを必要とする場合はボリュームをマウントする)
# 実行時には docker run --read-only オプションを付与する
WORKDIR /app
COPY . .
# 不要な権限を剥奪する(Capabilityの制限)
# docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE
—
4. Webアプリ層からの防御(WAFとIAMの連携)
コンテナへの攻撃は、多くの場合Webアプリケーションの脆弱性(RCEなど)から始まる。Python(Flask)やNode.js(Express)でバックエンドを組む際、以下の防御レイヤーを意識してほしい。
Node.js/Expressでのセキュアなヘッダー設定(helmet利用)
HTTPレスポンスヘッダーを適切に制御し、攻撃者が情報を探り当てるのを防ぐ。
const express = require('express');
const helmet = require('helmet');
const app = express();
// helmetはXSS、クリックジャッキング、MIMEタイプスニフィングを防ぐ標準的な保護層
app.use(helmet());
// 不要な情報をレスポンスヘッダーから削除
app.disable('x-powered-by');
app.get('/', (req, res) => {
res.send('Secure Response');
});
app.listen(3000);
—
最後に:セキュリティは「泥臭い作業」の積み重ね
セキュリティチーフとして最後に一つだけ言っておく。
「完璧なツール」など存在しない。重要なのは、「何が起きているのかを可視化すること」と「攻撃のコストを極限まで高めること」だ。
コンテナランタイムの脆弱性は、これからも形を変えて現れるだろう。しかし、Falcoでシステムコールを監視し、read-only ファイルシステムを徹底し、最小権限の原則を守り抜く。この泥臭い積み重ねが、君のサービスを守る最後の一線になる。
さあ、今すぐ君の環境の設定ファイルを開いて、root で動いているコンテナが一つでもないか確認することから始めよう。それがプロのエンジニアの第一歩だ。
コメント