Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Docker Bypasses UFW: 3 Ports I Denied Were Open to the Internet

2026年10月28日に「Dev.to」が公開したITニュース「Docker Bypasses UFW: 3 Ports I Denied Were Open to the Internet」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

DockerはUFWをバイパスし、拒否設定したポートも外部公開する危険性がある。DockerのNAT処理がUFWより優先されるためだ。対策は、内部ポートは公開せず、必要なポートは127.0.0.1にバインドし、daemon.jsonでデフォルトを127.0.0.1に設定する。外部からのポートスキャンで確認すべきだ。

ITニュース解説

システムエンジニアを目指す皆さんにとって、サーバーのセキュリティは非常に重要なテーマである。特に、アプリケーションの実行環境として広く利用されているDockerと、Linux標準のファイアウォール管理ツールであるUFWを組み合わせて使う際に、知っておくべき重要な落とし穴がある。この問題は、UFWの設定が正しくても、Dockerによって意図せずポートがインターネットに公開されてしまうというものだ。

この現象は「DockerがUFWをバイパスする」と表現される。具体的には、データベースやキャッシュサーバーなど、本来はサーバー内部でのみ利用されるべきポートが、インターネットに晒されてしまう可能性があるのだ。もしそうなれば、第三者からの不正アクセスや攻撃の標的となりかねない。例えば、本来許可されていないはずのPostgresやRedisのポートが、UFWのDENY設定にもかかわらず、外部から接続できてしまうという事態が発生する。UFWのstatusコマンドが「このポートは拒否されている」と表示していても、実際にはポートが開いているという、非常にたちが悪い状況なのだ。

なぜこのような問題が起こるのだろうか。その原因は、Linuxのネットワーク処理におけるDockerとUFWの動作順序にある。Dockerは、コンテナが外部と通信できるようにするためにNAT(Network Address Translation:ネットワークアドレス変換)という技術を利用する。これは、外部からのアクセスがあった際に、その宛先IPアドレスとポート番号をコンテナ内部のIPアドレスとポート番号に自動的に変換する仕組みである。このNAT処理は、Linuxカーネル内のネットワークパケット処理の早い段階で行われる。

具体的にパケットの動きを見てみよう。インターネットからあなたのサーバーの特定のポート(例えば5432番)にパケットが送られてくると、まず最初にNATテーブルの「PREROUTING」という処理チェーンを通過する。ここにDockerが設定したルールがあり、パケットの宛先がコンテナのIPアドレスとポート番号に書き換えられる。この時点で、パケットは「サーバー自身へ送られたもの」ではなく、「コンテナへ転送されるべきもの」として扱われるようになるのだ。

宛先が書き換えられたパケットは、次にルーティングの判断を受ける。サーバーは、このパケットがローカルのアプリケーション向けではなく、別のネットワーク(Dockerが作った仮想ネットワーク)上のコンテナ向けだと判断し、「転送(FORWARD)」処理を行う。この転送されるパケットは、フィルターテーブルの「FORWARD」チェーンを通過する。一方、UFWが設定する拒否ルールは、主にサーバー自身へ向かうパケットを処理する「INPUT」チェーンに適用される。

つまり、Dockerによって宛先が書き換えられ、FORWARDチェーンに送られたパケットは、UFWが監視するINPUTチェーンには決して入ってこないのだ。そのため、UFWが「このポートへのアクセスは拒否する」と設定していても、Docker経由で転送されるパケットはUFWの監視をすり抜けてしまい、結果としてポートが外部に公開されてしまうのである。

この問題が実際に自分のサーバーで発生しているかを確認するには、サーバー自身からではなく、別のマシン(自分のノートPCなど)からポートスキャンを行う必要がある。サーバーの内部でlocalhostに対してテストを行っても、パケットの経路が異なるため、この脆弱性は検出できない。nmapのようなツールを使って、nmap -Pn -p- SERVER_IPというコマンドでサーバーのすべてのポートをスキャンし、意図せず開いているポートがないかを確認することが重要だ。

この問題の典型的な発生例は、開発中にDocker Composeファイルにports: - "5432:5432"といった記述を追加し、ローカル環境でデータベースや管理ツールにアクセスできるようにしたまま、その設定が本番環境のサーバーにデプロイされてしまうケースだ。ports:という設定は、デフォルトでは0.0.0.0、つまりすべてのネットワークインターフェースにポートを公開してしまうため、意図せずインターネットにポートが公開される結果となる。パスワード保護されていないRedisのようなサービスが外部に公開されてしまえば、悪意のあるユーザーによって容易に侵入され、データが盗まれたり改ざんされたりする危険性がある。これは決して他人事ではなく、幸運に恵まれて被害がなかったとしても、それは「たまたま」であり、適切な対策が講じられていなかった証拠と言える。

では、このDockerによるUFWバイパスを防ぎ、安全にサーバーを運用するにはどうすればよいのだろうか。いくつかの対策があるが、優先順位が高いものから順に適用していくことを推奨する。

最初の最も確実な対策は、内部サービス用のポートをそもそも公開しないことだ。Docker Composeで同じネットワーク内にいるコンテナ同士は、サービス名を使って互いに通信できる。例えば、アプリケーションコンテナからデータベースコンテナへはdb:5432のようにアクセスできるため、データベースのポートをホストOSに公開する必要はない。ports:セクションからそのような記述を削除するだけで、外部からのアクセス経路を完全に遮断できる。古いComposeファイルなどで見かけるexpose:という設定は、他のコンテナへのサービス公開を意図するもので、ホストOSにポートを公開する機能はないため、そのまま残していても問題ない。

次に、どうしてもホストOSからアクセスしたいポートがある場合は、そのポートをループバックアドレス(127.0.0.1)にバインドするように設定する。Docker Composeであれば、ports: - "127.0.0.1:5432:5432"のように記述する。これにより、そのポートはサーバー自身(ローカルホスト)からしかアクセスできなくなり、インターネットからのアクセスは受け付けなくなる。外部からアクセスしたい場合は、SSHトンネルのような安全な方法を別途組み合わせることで、必要な時だけ安全に接続できるようになる。

さらに安全性を高めるための「セーフティネット」として、Dockerデーモンの設定ファイル(/etc/docker/daemon.json)で、ポート公開のデフォルトIPアドレスを127.0.0.1に設定する方法がある。{"ip": "127.0.0.1"}と記述し、Dockerを再起動することで、以降、docker run -p 5432:5432やCompose ports: - "5432:5432"のようにIPアドレスを明示しないポート公開は、自動的に127.0.0.1にバインドされるようになる。これにより、もし誤ってIPアドレスを省略してポートを公開しようとしても、意図せずインターネットに公開されてしまうリスクを軽減できる。ただし、この設定変更後に作成されたコンテナにのみ適用されるため、既存のコンテナは一度削除して再作成する必要がある点に注意が必要だ。

最後に、特定のポートをインターネットに公開する必要があるが、特定のIPアドレスからのみアクセスを許可したい場合など、より詳細なフィルタリングを行いたい場合は、DOCKER-USERというiptablesチェーンを活用する。Dockerは自身のiptablesルールを設定する際に、このDOCKER-USERチェーンは変更しないため、ユーザーが自由にルールを追加できる。このチェーンは、パケットがFORWARDチェーンに到達する直前で処理されるため、DockerによるNAT変換後のパケットに対してもフィルタリングを適用できる。注意点として、すでに宛先ポートが変換されている可能性があるため、conntrackモジュールを用いて元の宛先ポート(--ctorigdstport)を指定してルールを作成する必要がある。例えば、特定のIPアドレスからのアクセスのみを許可し、それ以外は拒否するといったルールを設定できる。UFWの管理に慣れている場合は、ufw-dockerのようなコミュニティプロジェクトを利用して、UFWのコマンドでこのDOCKER-USERチェーンを操作することも可能だ。

一方で、絶対に避けるべき「対策」もある。それはDockerの設定で"iptables": falseとすることだ。この設定を行うと、Dockerがiptablesの管理を行わなくなり、一見すると問題が解決するように思えるかもしれない。しかし、これによりコンテナがインターネットにアクセスできなくなったり、ポート公開が期待通りに機能しなくなったりと、さらに深刻でデバッグが困難な問題を引き起こす可能性がある。これは、安易な解決策のように見えて、かえって複雑なトラブルを生み出すため、推奨されない。

また、これらの対策に加えて、もう一段階上のセキュリティ層を設けることも重要だ。もしVPSプロバイダーがネットワークファイアウォールやセキュリティグループを提供しているのであれば、それを活用しよう。これらはホストサーバーの外部で動作するため、Dockerがiptablesの設定をどう変更しようとも影響を受けない。例えば、SSH、HTTP、HTTPSなど、本当に必要なポートだけをプロバイダー側のファイアウォールで許可することで、サーバー全体に対する最終的な防御壁を構築できる。

以上の点を踏まえ、安全なDocker運用のためには、以下のようなチェックリストを習慣にすると良いだろう。 まず、ports:セクションを修正するデプロイ後には必ず、外部からnmapコマンドでサーバーのポートスキャンを行い、意図しないポートが公開されていないかを確認すること。次に、docker-compose.ymlファイル内のports:エントリを一つ一つ確認し、127.0.0.1にバインドされていないものについては、その公開の正当性を慎重に検討すること。そして、Dockerホストの構築初期段階で/etc/docker/daemon.jsonに"ip": "127.0.0.1"を設定しておくこと。さらに、本番環境ではデータベースやキャッシュ、キューなどの内部サービスに対しては絶対にports:エントリを定義しないこと。最後に、利用しているVPSプロバイダーのファイアウォールやセキュリティグループで、本当に公開する必要があるポートだけを許可する設定を徹底すること。

この問題の最も恐ろしい点は、UFWが「拒否した」と報告し、Docker Composeも「正常に動作している」と見せかけるため、一見するとすべてが正しく機能しているように見えてしまうことだ。外部からのポートスキャンだけが、サーバーの本当のセキュリティ状態を教えてくれる。システムエンジニアとして、このような見えにくい潜在的なリスクを理解し、適切な対策を講じる能力は、安全なシステムを構築するために不可欠である。DockerとUFWの組み合わせにおけるこの特性を正しく理解し、安全な運用を心がけてほしい。

関連コンテンツ

関連IT用語