「まだコンテナを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)」の徹底について話をしよう。これもまた、攻撃者を絶望させる強力な武器になる。まずは今日のルートレス化、週明けまでに全環境に適用してみてくれ。期待しているよ。
コメント