Dockerの2375番ポートはなぜ危険?Carbonatoの手口と外部公開の対策

当ページのリンクには広告が含まれています。
Dockerの2375番ポートの外部公開に注意を促し、サーバーへの攻撃と鍵付きの盾を描いたイメージ

Docker APIを認証なしで2375番ポートへ外部公開すると第三者にコンテナを操作され、ホストまで乗っ取られるおそれがあります。2026年9月22日にThreatDownが報告した「Carbonato」は公開されたDocker APIを悪用して侵入し、到達できる別のDockerホストへ感染を広げるボットネットです。

目次

そもそも2375番ポートって何?

Linux上の一般的なDocker Engineは標準では/var/run/docker.sockというUnixソケットを通じて操作します。Docker APIはコンテナの作成や起動などをDockerデーモンへ指示するための管理用APIです。TCPで待ち受ける設定を追加するとネットワーク越しにこのAPIへ接続できるようになります。

接続方法ポート認証危険度
Unixソケット(標準)なし(ローカルのみ)OSのファイル権限低い(dockerグループの管理は別途必要)
TCP(認証なし)2375なし極めて高い
TCP(TLS+クライアント認証)通常2376クライアント証明書証明書と接続元の管理が必要

TCPの2375番は平文のDocker REST API、2376番はTLSを使うDocker REST APIのポートとしてIANAに登録されています。ですが2376番へ変更するだけで暗号化や認証が有効になるわけではなく、安全性を決めるのはTLS・クライアント認証の設定と接続を許可する範囲です。


IANAの登録簿の見方や登録された番号と異なるポートでもサービスを動かせる理由はポート番号の割り当てルールを解説した記事で確認できます。

なぜポートが1つ開いているだけでホストを乗っ取られるのか

Docker APIではコンテナの作成時にホストのディレクトリをマウントしたり特権モードを指定したりできます。ホストのプロセス空間やネットワーク空間を共有する設定もあります。これらはそれぞれ別の設定ですが、組み合わせてコンテナを起動するとホスト本体へ広くアクセスできてしまいます。

通常のLinux環境ではDockerデーモンがroot権限で動作するため、認証なしで管理APIを操作できる相手にもホストのroot権限に相当する操作を許してしまいます。この記事ではこの構成を前提に説明します。root権限を使わないRootlessモードでは影響範囲が異なりますが、管理APIを無認証で公開してよいわけではありません。

  • Docker自体の脆弱性ではなく正規の機能を悪用されている点が厄介です。パッチを当てても直りません。
  • 見直すべき箇所は公開範囲の設定で、ファイアウォールで80番や443番だけ開けているつもりでもクラウドのセキュリティグループ設定を1行間違えるだけで2375番が漏れることがあります。
  • 一度侵害されるとコンテナの中だけでなくホスト本体のファイルやプロセスまで触られてしまいます。

Carbonato攻撃に見る、侵害までの4ステップ

ThreatDownの調査によると、Carbonatoは認証なしで公開されたDocker daemonをインターネット上でスキャンし見つけたホストへ次の流れで侵入します。

  1. 公開された2375番ポートへHTTPでDocker APIのコンテナ作成エンドポイントを直接呼び出す。
  2. ホストの「/」をまるごとマウントしホストと同じプロセス・ネットワーク空間を共有する特権コンテナを起動する。
  3. nsenterでホストのPID 1(initプロセス)に入り込みコンテナ経由でホスト本体のコマンドを実行する。
  4. 侵入したホストを足がかりに同じネットワーク上で2375番が開いている他のDockerホストを5分おきにスキャンしてワームのように感染を広げる。

侵入後はリバースSSHトンネルによる持続的なアクセス確保、systemdの正規サービスを装ったプロセス名への偽装、cronやsystemdタイマーへの永続化なども確認されています。

さらに、オープンソースのAIエージェントフレームワーク「Hermes Agent」を導入し、Telegram経由で攻撃者が送るタスクをAIモデルが解釈してコマンドを実行する仕組みまで組み込まれている点がこの攻撃の特徴です。窃取対象としてAI関連のAPIキーが最優先で狙われていることも報告されています。

危険なのはDocker自体ではなく、認証なしで外部公開されたAPIが悪用されている点です。

自分の環境で2375番ポートが開いていないか確認する

Windowsから確認する

WindowsのPowerShellで管理しているDockerホストの名前またはIPアドレスを指定します。次のdocker-host.example.comは自分の対象ホストへ置き換えてください。

PowerShell
Test-NetConnection -ComputerName "docker-host.example.com" -Port 2375
実行結果(例)
ComputerName     : docker-host.example.com
RemoteAddress    : 192.0.2.10
RemotePort       : 2375
InterfaceAlias   : Ethernet
SourceAddress    : 192.168.1.100
TcpTestSucceeded : True

TcpTestSucceededがTrueならそのPCから対象ホストの2375番へTCP接続できています。とはいえ、この結果では「TCP接続ができている」だけの確認なので「Docker APIが応答しているか」「インターネット全体から到達できるか」までは分かりません。


接続に失敗したときの確認項目やPowerShellのバージョンによるコマンドの使い分けは次の記事で確認できます。

Linuxホストで待ち受けを確認する

Linux上でDocker Engineを動かしている場合はそのホストへログインして実行します。

Bash
sudo ss -ltnp 'sport = :2375'
実行結果(例)
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 0.0.0.0:2375 0.0.0.0:* users:(("dockerd",pid=1234,fd=3))

待受アドレスが127.0.0.1や::1ならループバック向け、0.0.0.0や[::]なら各アドレスファミリーの全インターフェース向けです。全インターフェースで待ち受けていても外部から届くかはファイアウォールやルーターなどの設定に左右されます。

Docker APIの応答と外部からの到達を確認する

同じホスト上で、ループバックの2375番から応答するかを確認する例です。

Bash
curl --connect-timeout 5 --max-time 10 http://127.0.0.1:2375/version
応答例(説明用に一部の項目だけを抜き出しています)
{
  "Version": "29.9.0",
  "Os": "linux",
  "Arch": "amd64"
}

Dockerのバージョン情報が返ればこの経路でAPIが応答していることを確認できます。特定のIPアドレスだけで待ち受けている場合はループバックへの接続に失敗することもあります。

外部からの到達を調べる場合は別回線のPCなどから対象ホストの公開IPアドレスまたは公開ホスト名を指定します。次のコマンドはLinux・macOSで実行する例です。

Bash
curl --connect-timeout 5 --max-time 10 http://docker-host.example.com:2375/version

別の端末でlocalhostを指定するとその端末自身へ接続してしまいます。必ず対象ホストのアドレスへ置き換えてください。

認証情報を渡さずにDockerのバージョン情報が返った場合はその確認元からAPIの情報を取得できています。通常の無認証Docker APIでは管理操作にもつながるため、公開設定を見直してください。読み取り結果だけですべての操作権限まで確認したとは判断しません。

2375番ポートを外部公開しないようにする設定

ここではsystemdで管理しているLinux上のDocker Engineを対象にします。Docker Desktopの設定とは異なります。

すでに外部へ公開している場合はまずクラウドのセキュリティグループやファイアウォールで2375番への接続を遮断します。そのうえでDockerのTCP待ち受けを解除します。Dockerの再起動でコンテナに影響が出る場合があるため、稼働中の処理を確認してから作業してください。

Step

設定されている場所を確認する

Bash
sudo systemctl cat docker.service
sudo systemctl cat docker.socket

docker.serviceのExecStartにある-Hまたは--hostの指定を確認します。docker.socket が存在する場合はListenStreamにTCPポートを追加していないかも確認してください。

/etc/docker/daemon.jsonがある場合はhostsの内容も確認します。変更前に編集するファイルのコピーを保存しておいたほうがいいですよ。

Step

TCP待ち受けの指定を外す

起動オプション側で待受先を管理している場合は次のコマンドで上書き設定を編集します。

Bash
sudo systemctl edit docker.service

既存のExecStartから不要なTCP待ち受けの-H tcp://...を外します。Dockerの実行パスやそれ以外の起動オプションは保持してください。

たとえば現在の起動行が次の場合

ini
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375

上書き設定は次の形になります。

ini
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd://

これは起動行の例です。実際の設定に--containerdなどの指定がある場合はそれらも残します。

このように起動オプション側で待受先を管理する構成ではdaemon.jsonに重複するhostsを残さないでください。他の設定項目は削除しません。

一方、起動オプションに-Hがなくdaemon.json側で待受先を管理している構成では、hostsから不要なTCPアドレスを外します。通常のLinux環境でUnixソケットだけを残す場合該当項目は次の形です。

Json
"hosts": ["unix:///var/run/docker.sock"]

これは既存JSON内の項目の例です。ファイル全体を上書きせず、ほかの設定やカンマの位置を保って編集してください。

docker.socketにも独自のTCP待ち受けを追加していた場合は追加した設定を解除します。

Step

再起動と待ち受け停止を確認する

Bash
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl status docker --no-pager
sudo ss -ltnp 'sport = :2375'

docker.socketのTCP待ち受け設定も変更した場合はソケット側にも変更を反映します。Dockerを停止するため稼働中のコンテナへの影響を確認してから実行してください。

Bash
sudo systemctl stop docker.service docker.socket
sudo systemctl daemon-reload
sudo systemctl start docker.socket
sudo systemctl start docker.service
sudo systemctl status docker.service docker.socket --no-pager
sudo ss -ltnp 'sport = :2375'

Dockerが正常に起動し最後のコマンドで2375番の待ち受けが表示されないことを確認します。何も表示されなくてもDocker自体の起動に失敗している場合は対処完了ではありません。

別回線からの接続確認も再実行します。別のポート番号でDocker APIを公開していた場合は、その待ち受けも確認してください。

どうしても外部からDockerを操作したい場合

CI/CDや複数人での共同管理などでどうしてもリモートからDockerを操作する必要がある場合、2375番を素のTCPで開けるのではなく次のどちらかを選びます。

  • SSH経由でdocker contextを作る方法。Docker公式でも案内されており、サーバー側にTLS証明書を用意せず、既存のSSH認証の仕組みを使えます。
  • TLSクライアント証明書を使う方法(2376番)。HTTP経由でしか接続できないツールと連携する場合はこちらを検討しますが、CA証明書の発行・失効管理まで必要になるため公式ドキュメントでも「TLSとCA管理は高度なトピック」と明記されています。

操作元にはDocker CLIとSSH接続環境が必要です。接続先のユーザーはサーバー上のDockerソケットへアクセスできる必要があります。次の例のユーザー名とホスト名は自分の環境へ置き換えてください。

コマンド
docker context create \
  --docker host=ssh://docker-user@your-server.example.com \
  --description="Remote engine" \
  my-remote-engine

作成した接続先を指定してDockerの情報を確認します。

コマンド
docker --context my-remote-engine info

いずれの方法でも対象ポートをインターネット全体ではなく特定のIPアドレスからだけ許可するようクラウド側のセキュリティグループやファイアウォールもあわせて絞り込んでおいてください。

迷いやすいポイント

Docker Desktopでも同じ危険はある?

Docker公式の現行設定一覧では「Expose daemon on tcp://localhost:2375 without TLS」はWindowsのHyper-Vバックエンド向けの項目で、既定では無効です。有効にするとlocalhost向けのTLSなしTCP接続を受け付けます。Docker DesktopのすべてのOS・バックエンドに共通する設定ではありません。

設定名のとおりlocalhost向けの待ち受けなのでそれだけで直接インターネットに晒されるわけではありませんが、共有PCの他ユーザーや、ポート転送・SSHトンネルを経由してlocalhostへ到達できる環境では、実質的に外部公開と同じ状態になります。

Docker公式のドキュメントでもこの設定は「TLSなしで公開するとリモートコード実行につながる可能性がある」と明記されています。

dockerグループに所属していることと何が違う?

ローカルのdockerグループへユーザーを追加する話とは別の問題です。dockerグループのメンバーはホスト上のroot権限相当の操作ができてしまうという意味では根っこは同じですが、あちらは「そのマシンにログインできるユーザーの権限」の話、今回の2375番公開は「インターネット上の誰でも」ホストを操作できてしまう話です。攻撃を受けやすいのは圧倒的に後者になります。

ポートさえ塞げばもう安心?

2375番を閉じてもすでに侵入されていた場合は安全な状態に戻ったとは限りません。攻撃者が別の接続経路や自動起動の仕組みを残している可能性があるためです。

ThreatDownはSOUL.md 内の「GH0ST」、.env内のCARBONATO_API_KEY、見覚えのないリバースSSHトンネルなどを確認の手がかりとして挙げています。

侵害が疑われる場合はホストを隔離してログなどの証拠を保全し、調査と復旧を進めます。保存していたAPIキーや認証情報は安全な別の環境から失効・再発行します。ホストの信頼性を確認できない場合は信頼できるイメージからの再構築も検討してください。

まとめ

  • Docker APIを2375番で認証なしに公開すると外部から特権コンテナ経由でホストごと乗っ取られる危険があるよ。
  • ホスト上での待ち受けと別回線からの到達を分けて確認してね。外から調べるときはlocalhostではなく対象ホストの公開アドレスを指定するよ。
  • 公開が不要ならdaemon.jsonとsystemdの設定からTCP待ち受けを外すのが一番確実だよ。
  • どうしてもリモート操作が必要ならTLSよりまずSSH経由のdocker contextを検討してみてね。
  • URLをコピーしました!
目次