Dockerデーモンの暗き側面:Unixソケットの権限濫用とリモートAPIの全貌
インフラエンジニアやクラウドアーキテクトであれば、日々のコンテナ運用のなかで docker.sock という文字列を無視することはできない。CI/CDパイプラインの構築、オーケストレーションツールの連携、あるいはコンテナ内からのDocker操作(いわゆるDocker-in-Docker)など、このUnixドメインソケットはあらゆる場所で静かに、かつ強大な権限を握っている。
しかし、セキュリティ監査の現場に立つ我々ホワイトハッカーから見れば、デフォルト設定のまま放置されたDockerデーモンは、インフラ全体へのバックドアを自ら進んで開放しているようなものだ。コンテナの脱獄(コンテナエスケープ)が容易にホストの完全な乗っ取りへと昇格する原因の多くは、このソケットのアクセス制御の不備、あるいは無防備に露出したTCPソケットに起因している。
今回は、Dockerデーモンが内包するアーキテクチャ上のリスク、特にUnixソケットの権限分離の不徹底と、TCPソケット経由のリモートAPIにおける認証の欠落が招く脆弱性の本質を解剖し、現場で即座に適用すべき堅牢化(ハーデニング)の実装手法を解説する。
—
1. Unixソケットの罠:なぜ docker グループへの所属は root 昇格と同義なのか
Dockerデーモン(dockerd)は、デフォルトで /var/run/docker.sock というUnixドメインソケットを作成する。このソケットに対する読み書き権限を持つプロセスは、DockerデーモンのAPIを完全に制御できる。
ここで多くの開発者やジュニアエンジニアが犯す致命的な誤解がある。「一般ユーザーでSSHログインし、作業効率化のために自分のアカウントを docker グループに追加する」という行為だ。セキュリティの観点から言えば、これは自分のユーザーアカウントに sudo なしでのフルルート権限を付与しているのと何ら変わらない。
攻撃シナリオ:グループ権限の悪用
もし攻撃者がWebアプリケーションの脆弱性(例えば、任意のファイル読み取りやコマンドインジェクション)を突いて、docker グループに所属する無害なアプリケーションユーザー(例:www-data や node)のコンテキストを奪取したとしよう。
攻撃者は、ホスト上にマウントされた、あるいはコンテナ内からアクセス可能な /var/run/docker.sock を通じて、以下のようなペイロードをデーモンに送り込むことができる。
{
"Image": "alpine:latest",
"Cmd": ["chroot", "/host", "10.0.0.1", "sh", "-c", "echo 'hacker ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers"],
"HostConfig": {
"Binds": ["/:/host"]
}
}
このリクエストは、ホストのルートファイルシステム全体を新規コンテナの /host にバインドし、ホストの /etc/sudoers を書き換えて永続的なバックドアを確立する。Unixソケットに対するファイルパーミッション(通常は root:docker で srw-rw----)の管理を誤れば、たった一つのコンテナの侵入がインフラ全体の崩壊を招くのである。
対策:グループレス運用と rootless モードへの移行
根本的な解決策は、ユーザーを docker グループに追加する運用を完全に廃止することだ。やむを得ずAPIを操作する必要がある場合は、デーモン自体をユーザー名前空間(User Namespaces)を利用した Rootlessモード で稼働させ、攻撃者がコンテナからホストのroot権限を奪うパスを物理的に断つべきである。
生産環境の dockerd 設定(/etc/docker/daemon.json)では、ユーザー名前空間の有効化を基本方針とする。
{
"userns-remap": "default"
}
これにより、Dockerコンテナ内のrootユーザーは、ホスト上では権限を持たない非特権ユーザーにマッピングされ、万が一のコンテナエスケープ時における被害を最小限に食い止めることができる。
—
2. リモートAPIの恐怖:暗号化なきTCPソケットの開放
「開発効率のために、別サーバーからDockerデーモンを遠隔操作したい」という要望から、しばしば dockerd -H tcp://0.0.0.0:2375 のような設定が施されることがある。
これはサイバー犯罪者や世界中のボットネットにとって、最も甘い蜜の一つだ。Shodanなどのインターネットスキャナーを叩けば、認証なしで開放されたポート 2375 や 2376 が今この瞬間も数千台単位でヒットする。認証機構を持たないTCPソケットは、ネットワーク越しに誰でも自由コンテナの起動や任意のコマンド実行を許可してしまう。実際、この設定ミスを突いたマイニングマルウェアの感染や、ランサムウェアによるデータ暗号化インシデントは後を絶たない。
リモートAPIを安全に利用するためには、通信の暗号化と、クライアント・サーバー間の厳格な身元確認を行う 相互TLS認証(mTLS: Mutual TLS) の強制が必須となる。
—
3. mTLSによるリモートAPI要塞化の実装手順
ここからは、実務のインフラ構築でそのまま投入できる、OpenSSLを用いた認証局(CA)の構築からDockerデーモンへのmTLS適用までの手順をコードベースで解説する。
ステップ1: 認証局(CA)と証明書の生成
まずは、サーバーとクライアントの証明書を署名するためのプライベートCAを構築する。作業用ディレクトリを作成し、以下のスクリプトを実行する。
# 1. 独自の認証局(CA)の秘密鍵と証明書を作成
openssl genrsa -aes256 -out ca-key.pem 4096
openssl req -new -x509 -days 3650 -key ca-key.pem -sha256 -out ca.pem \
-subj "/C=JP/ST=Tokyo/L=Chiyoda/O=SecOps/CN=Docker-Root-CA"
# 2. サーバー用の秘密鍵と証明書署名要求(CSR)を作成
# ※ <YOUR_SERVER_IP_OR_DNS> は実際のサーバーのIPアドレスまたはホスト名に置き換えること
openssl genrsa -out server-key.pem 4096
openssl req -subj "/CN=docker-server" -sha256 -new -key server-key.pem -out server.csr
# 3. サーバー証明書にSAN(Subject Alternative Names)を付与するための設定ファイルを作成
echo "subjectAltName = IP:192.168.1.100,IP:127.0.0.1,DNS:docker.internal" > extfile.cnf
echo "extendedKeyUsage = serverAuth" >> extfile.cnf
# 4. CAの署名によりサーバー証明書を発行
openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem \
-CAcreateserial -out server-cert.pem -extfile extfile.cnf
# 5. クライアント用の秘密鍵と証明書署名要求(CSR)を作成
openssl genrsa -out key.pem 4096
openssl req -subj "/CN=docker-client" -new -key key.pem -out client.csr
# 6. クライアント証明書用の拡張設定ファイルを作成
echo "extendedKeyUsage = clientAuth" > extfile_client.cnf
# 7. CAの署名によりクライアント証明書を発行
openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem \
-CAcreateserial -out cert.pem -extfile extfile_client.cnf
# 8. 不要になったCSRおよび拡張ファイルを削除し、秘密鍵のパーミッションを厳格化
rm -v client.csr server.csr extfile.cnf extfile_client.cnf
chmod 0400 ca-key.pem server-key.pem key.pem
chmod 0444 ca.pem server-cert.pem cert.pem
生成されたファイルのうち、サーバー側には ca.pem、server-cert.pem、server-key.pem を配置し、クライアント側には ca.pem、cert.pem、key.pem を安全に配布する。
ステップ2: Dockerデーモンの設定変更
サーバー側のDockerデーモンがmTLSを強制するように、設定ファイル /etc/docker/daemon.json を更新し、systemdのサービスユニットファイルを調整する。
/etc/docker/daemon.json:
{
"tlsverify": true,
"tlscert": "/etc/docker/certs/server-cert.pem",
"tlskey": "/etc/docker/certs/server-key.pem",
"tlscacert": "/etc/docker/certs/ca.pem"
}
次に、デーモン起動時にTCPソケットをバインドするよう、systemdのサービス設定を上書きする。
/etc/systemd/system/docker.service.d/override.conf (ファイルが存在しない場合は作成):
[Service]
ExecStart=
# Unixソケットに加え、指定したポートでTLSを強制したTCPソケットをリッスンする
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2376
設定を反映させ、Dockerサービスを再起動する。
# systemdの設定をリロード
sudo systemctl daemon-reload
# Dockerサービスを再起動して設定を適用
sudo systemctl restart docker
これで、ポート 2376 へのアクセスは、有効なクライアント証明書を提示しない限り、TLSハンドシェックの段階で完全に拒絶されるようになる。
—
4. 監査と検証:ゼロトラストの視点から
構築が完了したら、必ず外部のクライアント端末から接続テストを行い、セキュリティの防壁が正しく機能しているか監査を実施する。
有効な証明書を配置した環境からの接続テスト:
# 証明書が格納されたディレクトリ(例: ~/.docker)を指定して接続確認
docker --tlsverify \
--tlscacert=ca.pem \
--tlscert=cert.pem \
--tlskey=key.pem \
-H tcp://192.168.1.100:2376 version
もし証明書を指定せずに接続を試みた場合、以下のようなエラーが返され、TCPソケットが完全に保護されていることが確認できるはずだ。
Client sent an HTTP request to an HTTPS server. あるいは remote error: tls: bad certificate
—
まとめ:脆弱性の根本的解消に向けて
コンテナ技術はインフラの俊敏性を飛躍的に高めた一方で、カーネル共有というアーキテクチャ特性ゆえに、制御プレーン(Docker API)のセキュリティ担保が不十分であった場合の影響範囲は極めて甚大である。
「動けば良い」という安易な利便性の追求は、サイバー攻撃者にとって格好の侵入経路を提供する。「Unixソケットの権限管理の厳格化」「Rootlessモードの採用」「TCPリモートAPIにおけるmTLSの強制」。これらは現代のインフラストラクチャにおける最低限の衛生管理であり、プロフェッショナルなエンジニアが守るべき鉄則である。
日々の構築や運用のなかで、自らの手で脆弱性の芽を摘み取る。その泥臭い積み重ねこそが、真に信頼されるセキュアなシステムを形作る唯一の道なのだ。
コメント