【実務・中級編】 DockerコンテナのSeccompプロファイルによるシステムコール制限 – インフラ・ネットワーク & クラウドセキュリティ防御ガイド

コンテナの脱獄を許すな:Seccompで「システムコールの蛇口」を締め上げる

現場でインシデント対応をしていると、よく耳にするのが「コンテナを使っているから、ホストOSは安全だ」という甘い幻想だ。断言しよう。Dockerのデフォルト設定は、利便性を優先した「ザル」に等しい。

コンテナはホストのカーネルを共有している。つまり、もしアプリケーションに脆弱性があり、攻撃者がコンテナ内で任意のコードを実行できた場合、彼らはホストカーネルに対して直接「システムコール」を投げることで、コンテナの壁を突き破り、ホストOSを乗っ取ろうとする。

今日は、そんな悪夢を未然に防ぐ「Seccomp(Secure Computing Mode)」の極意を伝授する。

—

なぜデフォルトのDockerでは不十分なのか?

Dockerは標準で約300個のシステムコールを許可している。しかし、一般的なWebアプリケーションやAPIサーバーが、運用中に reboot や mount、あるいは ptrace(プロセス操作)なんて必要とするだろうか?

攻撃者は、これらの「本来不要だが許可されているシステムコール」を悪用して、カーネル内の脆弱性を突く。例えば、ptrace が有効であれば、コンテナ内のプロセスからホスト側のプロセスへ干渉を試みることも可能になる。

「必要な機能以外、すべて拒否する」。 これが要塞化(ハーデニング)の鉄則だ。

—

実践:Seccompプロファイルの実装

今回は、Node.js(JavaScript)で動作するAPIサーバーを例に、極限まで機能を絞ったプロファイルを作成する。

以下の設定ファイル profile.json を見てほしい。これは「最低限の動作」のみを許可し、それ以外をすべて拒否(SCMP_ACT_ERRNO)する設定だ。

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": [
    "SCMP_ARCH_X86_64",
    "SCMP_ARCH_X86",
    "SCMP_ARCH_X32"
  ],
  "syscalls": [
    {
      "names": [
        "read", "write", "exit", "exit_group", "epoll_wait",
        "futex", "mmap", "mprotect", "munmap", "brk",
        "rt_sigaction", "rt_sigreturn", "sched_getaffinity"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}
  • defaultAction: “SCMP_ACT_ERRNO”: これが肝だ。リストにない全てのシステムコールを、即座にエラーとして弾く。
  • syscalls: read, write, epoll_wait など、Node.jsのイベントループと通信に必要なものだけを厳選して許可している。

コンテナへの適用方法

作成したプロファイルをDockerコンテナに適用するのは簡単だ。--security-opt オプションを使う。

docker run --rm \
  --security-opt seccomp=./profile.json \
  -p 3000:3000 \
  my-secure-api:latest

もし、これ以外のシステムコールを攻撃者が実行しようとすれば、カーネルレベルで拒絶され、ログには「Operation not permitted」が記録される。攻撃者の試行は、その瞬間に無力化されるわけだ。

—

泥臭い運用現場での注意点

「厳しくしすぎてアプリが動かなくなった」という相談もよく受ける。特にNode.jsやPythonのようなインタプリタ言語は、実行時に動的にメモリ操作やシグナル処理を行うため、許可リストの微調整が必要になる。

トラブルシューティングのコツはこうだ:

1. 監査モードで実行する: プロファイルを SCMP_ACT_LOG モードで一度適用し、アプリケーションを動かしてみる。
2. ログを追う: dmesg や journalctl で、どのシステムコールが拒否されているのかを確認する。
3. 許可リストを更新する: 必要なものだけを profile.json に追加していく。

無理に最初から完璧を目指すと運用が止まる。まずは「明らかに不要なもの(mount, ptrace, kexec_load 等)」から禁止し、徐々に締め上げていくのが、現場を止めないセキュリティエンジニアの流儀だ。

—

最後に:セキュリティは「多層防御」

Seccompは強力な盾だが、これさえあればいいというものではない。以下の「基本」を忘れていないか?

  • 読み取り専用ファイルシステム: docker run --read-only を使う。
  • 非rootユーザー実行: Dockerfile内に USER node を記述し、root権限でプロセスを動かさない。
  • Capabilitiesの制限: --cap-drop=ALL で全ての権限を剥奪し、--cap-add=NET_BIND_SERVICE のように必要なものだけを追加する。

セキュリティとは、こうした「面倒なこと」の積み重ねだ。攻撃者は常に「最も防御が甘い部分」を突いてくる。このSeccompの設定が、君のシステムを狙う攻撃者にとって、乗り越えられない高い壁になることを願っている。

もし不明点があれば、またいつでも聞いてくれ。現場の最前線で戦うエンジニアを、私は全力でサポートする。

コメント

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