【実務・中級編】 コンテナ環境(Docker/Kubernetes)の脱獄とホストOSへの侵入 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

コンテナ脱獄(Breakout)の現実:Docker/K8s環境を「ただの隔離空間」と信じてはいけない理由

「コンテナは仮想マシンではない」――この言葉を、どれだけの開発者が真の意味で理解しているだろうか。

多くのエンジニアがコンテナを「軽量な仮想マシン」のように扱い、プロセスを隔離しているから安全だと信じ込んでいる。しかし、レッドチームの視点から言えば、デフォルト設定のコンテナは「うっかり鍵を開けたままにしてあるガレージ」に過ぎない。

今日は、コンテナ脱獄(Container Breakout)の悪夢と、それを確実に防ぐための防壁構築について、現場の知見を共有する。

—

1. コンテナ脱獄の「盲点」:なぜ突破されるのか?

コンテナ脱獄の基本原理は、「コンテナ内のユーザー権限が、実はホスト側のカーネルと直結している」という構造的欠陥を突くことにある。

よくある攻撃シーケンス:privileged モードの悪用

もし貴方の環境で --privileged フラグ付きでコンテナを動かしているなら、それは「ホストOSの管理者権限をどうぞ」と攻撃者に差し出しているのと同じだ。

1. 偵察: コンテナ内に侵入後、fdisk -l を叩いてみる。ホストのディスクが見えていれば勝負あり。
2. マウント: ホストのルートファイルシステムをコンテナ内にマウントする。

mkdir /tmp/host_root
   mount /dev/sda1 /tmp/host_root  # ホストのディスクをマウント

3. 特権奪取: ホスト側の /etc/shadow を書き換えるか、cron にバックドアを仕込めば、数秒後にはホストOSの root を掌握できる。

—

2. 現場で使える防御戦略:最小権限の徹底

「便利だから」という理由で特権を与えるのは禁止だ。以下の3つの防壁を構築せよ。

A. Docker側の防壁:Security Optの設定

コンテナの能力(Capability)を極限まで削る。cap-drop を使い、不要な権限を剥奪するのが鉄則だ。

# docker-compose.yml でのセキュアな設定例
services:
  web-app:
    image: my-secure-app:latest
    # 全ての権限を剥奪
    cap_drop:
      - ALL
    # 必要最小限の権限のみ許可(例: ネットワーク通信のみ)
    cap_add:
      - NET_BIND_SERVICE
    # ルートユーザーでの実行を禁止
    user: "1000:1000"
    # 書き込み専用のルートファイルシステム
    read_only: true

B. アプリケーションコードでの防御:ファイル操作の制限

Pythonでファイルアップロード機能を実装する場合、コンテナ外へのパストラバーサルは脱獄の入り口になる。

import os
import werkzeug.utils

# 脆弱な実装: ユーザー入力をそのままパスに連結してはいけない
# 安全な実装: ファイル名をサニタイズし、アップロードディレクトリを固定する
def secure_upload(request_file):
    UPLOAD_DIR = "/app/data/uploads"
    # ファイル名をクリーンアップし、パストラバーサルを防ぐ
    filename = werkzeug.utils.secure_filename(request_file.filename)
    save_path = os.path.join(UPLOAD_DIR, filename)
    
    # 物理的な保存先が想定外ではないかチェック
    if not os.path.abspath(save_path).startswith(UPLOAD_DIR):
        raise PermissionError("不正なファイルパスの検出")
        
    request_file.save(save_path)

C. KubernetesのPod Security Admission (PSA)

K8s環境では、PodSecurityContext を使い、コンテナがホストのプロセスを覗けないように制限する。

# pod-security.yaml
apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  securityContext:
    runAsNonRoot: true  # ルートユーザーでの実行を禁止
    runAsUser: 1000
    fsGroup: 2000
  containers:
  - name: app
    image: my-app
    securityContext:
      allowPrivilegeEscalation: false  # 特権昇格を完全に禁止
      capabilities:
        drop: ["ALL"]

—

3. 最後に:セキュリティは「設定」ではなく「文化」

ここまで技術的な設定を並べたが、結局のところ最大の脆弱性は「設定が面倒くさい」という人間の心理にある。

  • privileged: true を見つけたら即座に理由を問い詰めること。
  • 脆弱性スキャナ(TrivyやClair)をCI/CDパイプラインに組み込み、設定ミスをビルド段階で弾くこと。

「動けばいい」という開発フェーズは、セキュリティの観点では「穴だらけのまま出荷する」のと同じだ。我々エンジニアの仕事は、機能を作ることだけでなく、その機能が踏み台にされないよう、強固な防壁を設計することにある。

次回のデプロイ時、docker-compose.yaml を開くその指を一度止めて、そのコンテナが本当に「裸」ではないかを確認してほしい。それが、インシデントゼロを維持する唯一の道だ。

コメント

タイトルとURLをコピーしました