【実務・中級編】 コンテナのルートレスモード実行による攻撃影響の最小化 – インフラ・ネットワーク & クラウドセキュリティ防御ガイド

「まだコンテナをrootで動かしているのか?」

現場でインシデントハンドリングをしていると、喉まで出かかって飲み込む言葉があります。モダンな開発現場において、DockerやKubernetesは「標準装備」になりました。しかし、その中身が「root権限(UID 0)」で動いていることの危うさに無頓着なケースが、驚くほど多いのが現実です。

「コンテナは隔離されているから大丈夫だろう」——その油断こそが、我々ホワイトハッカーが侵入テスト(ペネトレーションテスト)で真っ先に突く「急所」です。

今日は、インフラ・ネットワークの要塞化(ハーデニング)における最重要項目の一つ、「コンテナのルートレス実行」について、攻撃者の視点と防御の実装を交えてディープに解説します。

—

1. なぜ「コンテナのroot」は致命的なのか:攻撃者のシナリオ

まず、厳しい現実を直視しましょう。デフォルト設定のDockerコンテナ内でroot権限を持っているということは、ホストOSのroot権限奪取まであと一歩の距離にいることを意味します。

攻撃手法(PoC)の裏側:コンテナ脱出(Container Escape)

例えば、あなたのWebアプリケーションに「RCE(遠隔コード実行)」の脆弱性があったとします。攻撃者はまず、コンテナ内でシェルを取ります。

もしコンテナがrootで動いていたら、攻撃者は以下の挙動を試みます:
1. デバイスファイルへのアクセス: /dev 以下のデバイスを操作し、ホストのハードディスクを直接マウントしにいく。
2. カーネル脆弱性の悪用: コンテナはホストとカーネルを共有しています。root権限があれば、カーネルの脆弱性(Dirty Pipe等)を突くエクスプロイトコードの実行が容易になります。
3. 権限の乱用: CAP_SYS_ADMIN などのケーパビリティが残っていれば、ホストのファイルシステムを書き換え、バックドアを仕掛けることができます。

「コンテナ内のroot」は、ホストOSから見れば「ただのrootプロセス」として見えているケースがほとんどなのです。これを防ぐ唯一の決定打が、ルートレス(Rootless)モードおよび非特権ユーザーでの実行です。

—

2. 実践:Dockerfileでの「脱・root」実装サンプル

まずは、アプリケーションレベルでできる最も確実な対策です。Dockerfile内で専用のユーザーを作成し、その権限でプロセスを起動します。

ここでは、脆弱性の標的になりやすいPHP(Laravel/Symfony等)を例に、セキュアなDockerfileの書き方を示します。

# --- セキュアなDockerfileの構築例 ---
FROM php:8.2-fpm-alpine

# 1. 必要なパッケージのインストール(最小限に絞る)
RUN apk add --no-cache shadow

# 2. アプリケーション専用の非特権ユーザーを作成
# UID/GIDを固定することで、ホスト側との権限管理を明確にする
ARG USERNAME=appuser
ARG GROUPNAME=appgroup
ARG UID=1000
ARG GID=1000

RUN groupadd -g $GID $GROUPNAME && \
    useradd -m -s /bin/bash -u $UID -g $GROUPNAME $USERNAME

# 3. 作業ディレクトリの設定と権限変更
WORKDIR /var/www/html
RUN chown -R $USERNAME:$GROUPNAME /var/www/html

# 4. 【重要】実行ユーザーをrootから appuser に切り替え
# これ以降の命令や、コンテナ起動時のプロセスはこのユーザーで実行される
USER $USERNAME

# 5. アプリケーションコードのコピー
COPY --chown=$USERNAME:$GROUPNAME . .

# 6. ポートの制約
# ルートレスの場合、1024番未満のウェルノウンポートは使えないため8080等を使用する
EXPOSE 8080

CMD ["php-fpm"]

現場のTips: ポート番号の罠

ルートレスで動かす際、初心者が必ずハマるのが「80番ポートが使えない」という問題です。Linuxの仕様上、1023番以下のポート(特権ポート)はrootしかバインドできません。コンテナ内でも8080番や9000番を使い、外側のWAFやロードバランサー(ALB/Nginx)で80/443を受け止めるのが鉄則です。

—

3. インフラ・オーケストレーション層での要塞化

Dockerfileで USER を指定するだけでなく、実行環境(Docker ComposeやKubernetes)側でも制約を課すことが重要です。「設定漏れ」というヒューマンエラーを防ぐための二重の鍵です。

Docker Composeでの制約設定

docker-compose.yml において、不必要な特権を剥奪(Drop)し、読み取り専用のファイルシステムを適用する例です。

version: '3.8'
services:
  web-app:
    build: .
    user: "1000:1000" # 明示的にUID/GIDを指定
    security_opt:
      - no-new-privileges:true # プロセスの権限昇格を禁止
    read_only: true # コンテナのファイルシステムを読み取り専用に(攻撃者のファイル設置を防ぐ)
    tmpfs:
      - /tmp # 書き込みが必要な場所だけメモリ上に制限して許可
      - /var/run/php-fpm
    cap_drop:
      - ALL # すべてのケーパビリティ(特権操作)を一旦捨てる
    cap_add:
      - CHOWN # 必要最低限の権限だけをホワイトリスト形式で戻す(通常は不要)
    networks:
      - backend

networks:
  backend:
    internal: true # 外部への不要な通信を遮断

—

4. ホストOS側の設定:Docker Rootless Modeの導入

さらに踏み込んだ対策として、Dockerデーモンそのものを一般ユーザー権限で動かす「Rootless Mode」があります。これにより、万が一Dockerの脆弱性を突かれてデーモンが乗っ取られても、ホストOSのroot権限は守られます。

導入のポイント(Ubuntu等のLinux環境)

1. 依存パッケージの導入: uidmap が必要です(User Namespaceを利用するため)。

sudo apt-get install -y dbus-user-session uidmap

2. インストールスクリプトの実行:

curl -fsSL https://get.docker.com/rootless | sh

3. 環境変数の設定: .bashrc 等に以下を追加します。

export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock

これで、ps aux | grep dockerd を叩いた時に、実行ユーザーが root ではなくあなたの一般ユーザー名になっていれば成功です。

—

5. セキュリティチーフからの助言:運用での「盲点」

ルートレス化を導入すると、開発チームから必ずと言っていいほど「ログが書き込めない」「パーミッションエラーが出る」という不満が出ます。ここで安易に chmod 777 を許可してはいけません。

  • 共有ボリュームの罠: ホストのディレクトリをマウントする場合、ホスト側のUIDとコンテナ内のUIDを一致させるか、bind mount の代わりに docker volume(名前付きボリューム)を使用することを検討してください。名前付きボリュームであれば、Dockerが適切に権限を管理してくれます。
  • サイドカーパターンの活用: ログ出力や証明書更新など、どうしても高い権限が必要な処理は、メインのアプリコンテナに持たせず、権限を絞った別コンテナ(サイドカー)に分離してください。

まとめ

コンテナのルートレス実行は、単なる「推奨設定」ではなく、現代のサイバー攻撃に対する「防波堤」です。

1. Dockerfileで USER を指定する。
2. 特権ポート(80/443)を避け、8080等を利用する。
3. Docker ComposeやK8sの securityContext で特権昇格を禁止する。

この3点を徹底するだけで、あなたのシステムが「標的」にされた際の生存率は劇的に向上します。泥臭いパーミッション調整の先にこそ、真に堅牢なインフラが宿るのです。

後輩諸君、次は「読み取り専用ファイルシステム(Read-only RootFS)」の徹底について話をしよう。これもまた、攻撃者を絶望させる強力な武器になる。まずは今日のルートレス化、週明けまでに全環境に適用してみてくれ。期待しているよ。

コメント

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