コンテナを開ける四つの扉
一言でいうと
コンテナ脱出は、たいていカーネルの脆弱性ではなく、自分たちが直接開けておいた設定から 起こります。4つのパターンを知っていれば、レビューで30秒で見つけられます。
このレッスンは、防御のためのものです。各項目ごとに、「なぜ危険か」と 「何で防ぐか」をペアにして扱います。
なぜ必要なのか
コンテナが非rootかを確認するだけでは、ホストの境界を証明できません。 Dockerソケット、ホストのファイルシステム、PIDネームスペースや過剰なcapabilityを開けば、 コンテナ内の低い権限のプロセスでも、ホストの権限を迂回して獲得できます。 したがって、イメージのユーザーだけでなく、ランタイムに接続したリソースとカーネルの権限もあわせてレビューする必要があります。
扉1: Dockerソケットのマウント
volumes:
- /var/run/docker.sock:/var/run/docker.sock
CIランナーやモニタリングエージェントでよく見かける行です。このソケットは、Docker デーモンのAPIであり、デーモンはホストのrootで動いています。ソケットにアクセスできれば、 ホストのルートを丸ごとマウントした特権コンテナを、新しく起動できます。 つまり、この1行は、事実上ホストのroot権限を与えるのと同じです。
防ぐ方法: ソケットをコンテナに渡しません。コンテナのビルドが必要なら、 デーモンが不要なビルダー(kaniko、buildah)を使い、情報の取得が目的なら、 読み取り専用のプロキシを前に置いて、許可するエンドポイントを許可リストで制限します。 LabHubのイメージビルドがkanikoを使う理由が、これです。
扉2: --privileged
--privilegedは、capabilityをすべて与え、デバイスへのアクセスを開き、
seccomp/AppArmorを事実上解除します。この状態では、ホストのディスクを
/dev/sdaとして開いてマウントすることが可能です。
防ぐ方法: 必要なcapabilityだけを個別に与えます。たとえば、低いポートへの
バインドが目的なら、NET_BIND_SERVICE1つで足ります。Kubernetesなら、
Pod Security Admissionのbaseline以上で、特権Podをそもそも拒否します。
扉3: ホストパスのマウント
volumes:
- /:/host # 최악
- /etc:/etc-host # 나쁨
- /var/log:/logs # 상황에 따라
このコードブロックの韓国語コメントは、順に、最悪、悪い、状況による、という意味です。
/は言うまでもなく、/etcだけを使っても、shadow・sudoers・cronファイルに
届きます。書き込み可能なら、そのままホストの侵害です。
防ぐ方法: マウントのパスを最小限の範囲に絞り、:roを付けます。
ホストのパスの代わりに名前付きボリュームを使い、Kubernetesでは、ポリシーエンジンで
hostPath自体を禁止するのが確実です。
扉4: 共有されたネームスペース
--pid=hostは、ホストのすべてのプロセスを見えるようにし、--net=hostは、
ホストのネットワークスタックをそのまま使います。前者は/proc/<pid>/rootを通じて、
ほかのプロセスのファイルシステムにアクセスする道を開き、後者は、localhostにだけ
開けておいた管理ポートに届くようにします。
防ぐ方法: デフォルト(分離)を維持します。モニタリングのように、本当に必要な場合でも、 読み取り専用 + 最小限のcapabilityに絞ります。
一目でわかる一覧
| 設定 | 実質的な意味 | 代替 |
|---|---|---|
docker.sockのマウント |
ホストのroot | kaniko/buildah、読み取り専用のプロキシ |
--privileged |
すべてのcapability + デバイス | 必要なcapだけ、PSA baseline |
-v /:/host |
ホストのファイルシステム | 範囲の縮小 + :ro、名前付きボリューム |
--pid=host |
ほかのプロセスへのアクセス | デフォルトの分離を維持 |
そして、防御の層
設定をうまく行っても、ランタイムの脆弱性は残ります。層を重ねておきます。
- UID:
USER 10001。rootで動かさなければ、ほとんどの経路が塞がります。 - capability: すべてdropして、必要なものだけをaddします。
- NO_NEW_PRIVS: setuidで権限を上げる道を断ちます。
- 読み取り専用ルート:
--read-only+ 必要な場所だけtmpfs。 - seccomp: 危険なシステムコールを、カーネルの入口で遮断します。
LabHubのラボのPodは、この5つをすべて適用しています。そのため、皆さんがラボの
中でrootとして何をしても、ホストには届きません。
現場での姿
- CIランナーがソケットをマウントしている → リポジトリにコミットできる人なら、 誰でもホストを乗っ取れる状態です。
- 「とりあえずprivilegedでやってみて、動いたら減らそう」 → 減らすコミットは来ません。
- ログ収集ツールが
/var/logをrwで付けている → 監査ログを消せます。
次の確認で見ること
続くクイズでは、Dockerソケット・ホストのマウント・PIDの共有が開く経路と、それを 防ぐ最小限のcapability・NoNewPrivs・seccompの役割を区別します。前のラボで確認した カーネルの権限の値を根拠に、単一の防御策で十分だという誤答を除いてください。