【テクニカル・上級編】 Capabilitiesの最小化(Drop All Capabilities) – インフラ・ネットワーク & クラウドセキュリティ防御ガイド

「rootに頼るな」は甘い:Linux Capabilitiesの完全剥奪がもたらす本質的防衛

多くのエンジニアが「コンテナはrootで動かさない」という標語を金科玉条のように信じている。だが、現実はどうか。DockerやKubernetesのデフォルト設定は、利便性の名の下に、攻撃者にとっての「夢の広場」を放置している。

本稿で語るのは、単なる権限設定の話ではない。カーネル境界を跨ぐエクスプロイトを無力化し、万が一のRCE(リモートコード実行)が発生しても、攻撃者を「檻」の中に閉じ込めるための、泥臭くも精緻なアーキテクチャ設計だ。

1. なぜ CAP_SYS_ADMIN を許してはいけないのか

Linuxの権限モデルにおける Capabilities は、かつてのバイナリ単位の特権を細分化したものだ。しかし、多くの開発者は「とりあえず動くから」という理由で、コンテナランタイムがデフォルトで付与する一連の権限を放置している。

特に CAP_SYS_ADMIN は「第二のroot」とも呼ばれる禁断の果実だ。これを持つコンテナは、名前空間の操作、マウントの実行、さらにはカーネルモジュールのロードすら可能にする余地がある。CVE-2022-0492(cgroup v1の脆弱性)のような深刻な権限昇格エクスプロイトが猛威を振るった際、この権限が剥奪されていれば、被害はコンテナ内という極めて狭い範囲に限定されたはずだ。

2. 「Drop All」の実践:防衛の第一歩

我々アーキテクトが目指すべきは、「デフォルトで全剥奪、必要なものだけを最小限許可」というホワイトリスト運用だ。KubernetesにおけるPod Security Admission(PSA)のポリシー設定を例に見てみよう。

# Pod Security Standard "Restricted" を適用したセキュリティコンテキストの例
apiVersion: v1
kind: Pod
metadata:
  name: hardened-app
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  containers:
  - name: app-container
    image: my-secure-app:latest
    securityContext:
      allowPrivilegeEscalation: false # 特権昇格の試行をカーネルレベルでブロック
      capabilities:
        drop:
          - ALL # 最初に全ての権限を剥奪する
        add:
          - NET_BIND_SERVICE # 1024以下のポートで待ち受ける場合のみ許可

この設定において重要なのは allowPrivilegeEscalation: false だ。これが false であれば、no_new_privs フラグがカーネルにセットされる。たとえコンテナ内で setuid ビットが立ったバイナリを実行しようとしても、システムはそれを拒絶する。これは、メモリ破壊脆弱性を突いてシェルを取られた後の「横展開」を防ぐための、最後の砦となる。

3. 低レイヤから見る防御ロジック:なぜ「剥奪」が効くのか

サイバー攻撃者の視点に立とう。彼らは標的のOSに侵入した際、まず uname -a や /proc/self/status を見て、現在の権限セットを確認する。もし CAP_SYS_PTRACE があれば、彼らはホスト上の他のプロセスにアタッチし、メモリ内の暗号鍵やTLSセッションをダンプし始めるだろう。

また、パケット構造の解析において CAP_NET_RAW を保持していると、ARPスプーフィングやパケットインジェクションが可能になる。これらはネットワーク境界防御を無効化する強力な武器だ。

これらの権限を一切持たないプロセスは、カーネルから見れば「単なる計算リソース」に過ぎない。たとえ libc の脆弱性を突いて任意コードを実行しようとしても、カーネル側で ptrace や bpf システムコールが制限されていれば、彼らの攻撃コード(シェルコード)は実行の機会を失う。

4. 生成AI時代のガードレイルと監査の統合

現代のシステムでは、生成AIを組み込んだアプリケーションがコンテナ上で動くケースも多い。ここで恐ろしいのは、LLMに対するプロンプトインジェクションが、最終的にOSのシステムコール実行を伴うエクスプロイトに繋がることだ。

我々は、単にCapabilitiesを剥奪するだけでなく、seccomp プロファイルによるシステムコールフィルタリングを併用すべきだ。

/* seccomp プロファイル例: 不要なシステムコールをブロックする */
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64"],
  "syscalls": [
    {
      "names": ["read", "write", "exit", "futex"],
      "action": "SCMP_ACT_ALLOW"
    }
    // execve 等の危険なシステムコールをここで明示的に除外する
  ]
}

最後に:セキュリティは「諦め」から始まる

完璧な防御など存在しない。しかし、敵のコストを跳ね上げることは可能だ。「rootで動くコンテナ」を見つけるたびに、我々は敵に無料のパスポートを渡しているのと同じことだ。

インフラエンジニア諸君、今日から drop: ["ALL"] をCI/CDパイプラインの必須ゲートとして組み込んでほしい。それが、複雑化するクラウド環境において、我々アーキテクトが守らなければならない最後の防衛ラインだ。

システムがクラッシュするのを恐れるな。適切に制限された環境で動かないならば、それは設計が脆弱であるというサインだ。そのサインを真摯に受け止め、コードを修正する。その泥臭いプロセスこそが、真の「強固な要塞」を築くための唯一の道である。

コメント

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