【実務・中級編】 コンテナのPID名前空間分離とプロセス監視 – インフラ・ネットワーク & クラウドセキュリティ防御ガイド

おい、ちょっと手を止めてくれ。インフラやコンテナの設計書を見直しているところか?
「Dockerを使っているから、うちはホストから完全に隔離されている」――そんな風に油断しているなら、今すぐその甘い認識を改めよう。

数々のインシデント現場を渡り歩いてきた私から言わせれば、デフォルト設定のまま運用されているコンテナ環境など、鍵の開いたガラス張りのショーケースのようなものだ。特に、プロセス管理の根幹であるPID名前空間(PID Namespace)の分離をサボっている環境は、攻撃者に足場を与えているのと同じだ。

今回は、コンテナ内でのプロセス隠蔽やホストへの不正干渉を防ぐための「PID名前空間の分離」と、そこを突き破ろうとする攻撃者の手口、そして現場で即座に使える具体的なハードニング手法を徹底的に解説しよう。

—

1. なぜPID名前空間の分離が不十分だと危険なのか?(攻撃者の視点)

Linuxカーネルの機能である「名前空間(Namespaces)」は、コンテナ技術の根幹を支える技術だ。ネットワークやファイルシステム、ユーザーIDなどを分離することで、コンテナあたかも「独立した仮想マシン」であるかのように振る舞わせる。

しかし、デフォルトのDocker設定や、誤ったオーケストレーション設定では、ホストとコンテナの間でPID名前空間が共有(--pid=host)されていたり、あるいは単にコンテナ内の監視が甘い状態になっていることがある。

攻撃シナリオ:コンテナからのホスト制圧

もし攻撃者がWebアプリケーションの脆弱性(RCE:リモートコード実行など)を突き、コンテナ内への足がかりを得たとしよう。
ここでPID名前空間がホストと共有されている、あるいは適切に制限されていない場合、コンテナ内のユーザーは次のような悪行を働くことができる。

1. ホストプロセスの列挙と把握:
コンテナ内からホスト上で稼働しているすべてのプロセス(データベースのマスタープロセス、SSHデーモン、他のコンテナの管理プロセスなど)が丸見えになる。
2. シグナル攻撃(DoSやプロセスの強制終了):
権限さえあれば、ホスト側の重要プロセスに対して kill シグナルを送り、サービスを停止させることが可能になる。
3. プロセスの隠蔽と偽装:
自らが実行したバックドアや不正なマイニングツールのプロセスを、ホスト側の正規のプロセス名に偽装して紛れ込ませる。

「うちのアプリは非特権(Non-root)で動いているから大丈夫」という言い訳は通用しない。カーネルの脆弱性やコンフィグの不備が重なれば、コンテナの壁など紙細工のように崩れ去る。

—

2. 徹底防御:PID名前空間の分離とコンテナランタイムの設定

このリスクを断ち切るためのアプローチはシンプルかつ絶対的だ。「ホストとコンテナのPID名前空間を完全に切り離し、コンテナ内からはコンテナ自身のプロセスしか見えないようにする」こと。そして、プロセス監視の目を光らせることだ。

Docker環境での設定

Dockerでコンテナを起動する際、明示的にPID名前空間を分離する必要がある(通常、デフォルトで分離されるが、設定ファイルやComposeファイルで意図せず共有されていないか確認が必須だ)。

以下の docker-compose.yml の実装サンプルを見てほしい。インフラ設計の現場でそのまま適用できるセキュアな構成だ。

version: '3.8'

services:
  web_application:
    image: python:3.11-slim
    restart: always
    # 【重要】PID名前空間をホストと共有しない(デフォルトだが明示的に意図を示す)
    # ホストのプロセスが見えないようにし、コンテナ内のPID 1をアプリケーションのinitプロセスにする
    pid: "private"
    
    # セキュリティをさらに高めるための追加ハードニング
    security_opt:
      - no-new-privileges:true # 特権昇格の防止
    cap_drop:
      - ALL # すべてのLinux能力(Capabilities)を一度剥奪
    cap_add:
      - NET_BIND_SERVICE # ポート80/443バインドなど、最小限の権限のみ付与
    
    # リソース制限の設定(DoS対策)
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 512M
    
    volumes:
      - ./app:/app:ro # アプリケーションコードは読み取り専用でマウント

—

3. コンテナ内からのプロセス監視と異常検知(Python実装例)

名前空間を分離したとしても、コンテナ内部で不正なプロセスがスパウン(生成)されるリスクはゼロではない。Webアプリの脆弱性からシェルを奪われた際、攻撃者は一刻も早く情報収集や追加ツールのダウンロードを行おうとする。

ここで、アプリケーション層(またはサイドカーコンテナ)から、コンテナ内のプロセスツリーを常時監視し、不審なプロセス(例: nc, bash, python によるリバースシェルなど)が起動した瞬間に検知・アラートを上げる仕組みが現場では重宝する。

以下のPythonスクリプトは、軽量にコンテナ内のプロセスをスキャンし、ブラックリストに該当するコマンド検知時に警告を発する監視エージェントの実装サンプルだ。

import os
import time
import psutil
import logging

# ロギングの設定
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] SEC-AGENT: %(message)s'
)

# 監視対象とする危険なプロセス名(攻撃者がよく使うツールやシェル)
SUSPICIOUS_PROCESSES = {
    'nc', 'ncat', 'netcat', 'socat', 
    'bash', 'sh', 'zsh', 
    'python', 'perl', 'ruby', 'php' # これらが予期せぬ引数で動いていないか監視
}

def monitor_processes():
    logging.info("コンテナ内プロセス監視エージェントを起動しました。")
    
    while True:
        try:
            # 現在稼働中のプロセスをイテレート
            for proc in psutil.process_iter(['pid', 'name', 'cmdline', 'username']):
                try:
                    pinfo = proc.info
                    p_name = pinfo.get('name', '').lower()
                    p_cmdline = pinfo.get('cmdline', [])
                    
                    # プロセス名がブラックリストに含まれているかチェック
                    if p_name in SUSPICIOUS_PROCESSES:
                        # ※正規のアプリケーションとして稼働しているものは除外するロジックをここに挟む
                        # 例: Pythonアプリ自体が動いている場合、自身のPIDは除外するなど
                        if p_info['pid'] == os.getpid():
                            continue
                            
                        cmd_str = " ".join(p_cmdline) if p_cmdline else "N/A"
                        logging.warning(
                            f"【警告】不審なプロセスを検知しました! "
                            f"PID: {p_info['pid']}, Name: {p_name}, Cmd: {cmd_str}, User: {pinfo.get('username')}"
                        )
                        
                        # 本番環境ではここで自動的にプロセスを強制終了(kill)する措置も検討可能
                        # proc.terminate()
                        
                (except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
                    pass
                    
        except Exception as e:
            logging.error(f"プロセス監視ループ内でエラーが発生しました: {str(e)}")
            
        # 10秒ごとにスキャンを実行
        time.sleep(10)

if __name__ == "__main__":
    monitor_processes()

このようなスクリプトをサイドカーとして配置するか、アプリケーションのヘルスチェックプロセスに組み込むことで、万が一の侵入時も「侵入された事実」を最速で察知し、ラテラルムーブメント(横展開)を阻止することができる。

—

4. セキュリティチーフからの実務アドバイス

インフラの要塞化において、「設定したから終わり」という魔法の杖は存在しない。今回解説したPID名前空間の分離も、Kubernetes環境であれば hostPID: false(デフォルト)が確実に入っているか、PodSecurityStandardsが厳格に適用されているかをCI/CDパイプラインの静的解析(ConftestやCheckovなど)で常にチェックする仕組みが不可欠だ。

チームの後輩や開発メンバーによく言うことだが、「攻撃者はコンテナの壁を突破する前提で動いている。突破された後に、いかに行動範囲を狭め、いかに早く気づけるか」がセキュリティエンジニアの腕の見せ所だ。

今日のデプロイから、ホストとコンテナの境界線が曖昧になっていないか、今一度設定を厳しく見直してほしい。セキュリティは細部に宿る。頼んだぞ。

コメント

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