開けたまま忘れられたポートは、サーバーへの侵入経路の定番です。公開アドレス上のデータベース、デバッグ用ダッシュボード、誰も意図していないポートを公開したコンテナ。2つのコマンドと1つの落とし穴を知っていれば、確認は1分で終わります。以下はすべて当社イメージから作った新しいUbuntu 24.04サーバーで実行し、2台目のマシンからスキャンしたものなので、出力はご自身のサーバーで見るものと同じです。
1. 何が待ち受けているか
コマンドはssです。オプションの文字は、TCP、UDP、待ち受け中のソケット、数値のポート番号、プロセス名を意味します。
sudo ss -tulpn
新しいサーバーでは6行が表示されます。
Netid State Local Address:Port Process
udp UNCONN 127.0.0.54:53 systemd-resolve
udp UNCONN 127.0.0.53%lo:53 systemd-resolve
tcp LISTEN 127.0.0.54:53 systemd-resolve
tcp LISTEN 127.0.0.53%lo:53 systemd-resolve
tcp LISTEN 0.0.0.0:22 sshd
tcp LISTEN [::]:22 sshd
重要なのはLocal Address列です。誰が接続できるかを示します。
| アドレス | 到達できる相手 |
|---|---|
127.0.0.1:…または127.0.0.53 | このサーバー上のプログラムだけ |
0.0.0.0:… | ファイアウォールが遮断しない限り、IPv4で誰でも |
[::]:… | IPv6で誰でも(通常はIPv4も) |
10.0.0.5:…(特定のアドレス) | そのアドレスに到達できる人なら誰でも |
つまり新しいサーバーが外部に公開しているのは1つだけで、IPv4とIPv6のポート22のSSHです。127.0.0.53のDNSリゾルバーはプライベートです。
インターネットから見える可能性があるものだけを一覧にするには、ループバックアドレスを除外します。
sudo ss -tlnp | awk 'NR>1 && $4 !~ /^(127\.|\[::1\])/ {print $4, $6}'
インストールのたびに実行してください。nginx、Docker、小さなPythonアプリを入れたところ、一覧は2行から10行に増え、その中には0.0.0.0:8081のdocker-proxyや、ランダムなlocalhostポートのcontainerdのソケットがありました。
2. どのプロセスがポートを使っているか
ポートが使用中で、誰が使っているか知りたいとき:
sudo lsof -i :80 -P -n
sudo fuser 80/tcp
sudo ss -tlnp 'sport = :80'
最も読みやすいのはlsofです。nginxではrootのマスタープロセスとwww-dataのワーカーが表示され、どちらもIPv4とIPv6の*:80でした。fuserはPIDだけを表示するので、スクリプトで便利です。ポートを空ける必要があるときは、PIDをkillするのではなくサービスを止めてください。そうしないとsystemdが再起動します:sudo systemctl stop nginx。
3. インターネットから何が見えるか
ssはサーバー側の視点を示します。インターネット側の視点は異なることがあります。間にファイアウォールがある場合や、サービスが127.0.0.1にバインドされている場合です。実際の状態を見るには、サーバーを別のマシンからスキャンします。ノートPCでも2台目のサーバーでも構いません。ツールはnmapです(apt install nmap、WindowsとmacOSはnmap.orgのインストーラー)。
nmap your-server-ip
nmap -p- your-server-ip
nmap -p 22,80,443,3306 your-server-ip
1つ目の形式はよく使われる1,000ポート、2つ目は全65,535ポート、3つ目は指定したリストを調べます。ファイアウォールを入れる前の新しいサーバーでの結果です。
Not shown: 999 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
Nmap done: 1 IP address (1 host up) scanned in 6.83 seconds
全65,535ポートのフルスキャンは8.9秒で終わり、同じ1つのポートが見つかりました。閉じたポートはすぐにリセットで応答するため、スキャンが速いのです。
重要な状態は3つです。
- open:プログラムが接続を受け入れました。これが攻撃対象領域です。
- closed:そこでは何も待ち受けていないとサーバーが応答しました。安全ですが、サーバーが存在し、何を動かしていないかがわかります。
- filtered:応答なし。ファイアウォールがパケットを破棄しました。スキャナーはその裏にサービスがあるかどうかわかりません。
何もインストールせずに1つのポートを調べるなら、nc -zv サーバーのIP 443がsucceededかrefusedを返し、フィルターされたポートではタイムアウトまで待ちます。UDPはnmapに-sUを付けますが、時間がかかります。当社のサーバーでは、よく使われるUDPポートの53、123、161はすべてclosedでした。
NATプランについての注意:インターネットから届くのは、当社がお客様に転送したSSHポートだけです。共有アドレスをスキャンすると見えるのは当社のネットワークの境界で、お客様のサーバーではありません。ほかのポートは、何が待ち受けていても到達できません。専用のIPv4アドレスがあるプランでは、開いているポートはすべてお客様自身で管理します。
4. 不要なものを閉じる
方法は3つあり、上から順に望ましいものです。
localhostにバインドする。 サーバー上のプログラムだけが使うサービスなら、127.0.0.1で待ち受けさせます。--bind 127.0.0.1 8080で小さなWebアプリを起動したところ、ssには127.0.0.1:8080と表示され、外部からのnmapは8080/tcp closedと報告しました。データベースではこれが正しい初期設定です。UbuntuのRedis、MySQL、PostgreSQLはインストール直後からlocalhostで待ち受けており、当社のRedisとMySQLのガイドもその設定のままにしています。
サービスを止める。 sudo systemctl disable --now 名前で待ち受けを完全に取り除けます。ssで消えたことを確認してください。
ファイアウォールを有効にする。 まずSSHを、次に提供するものを許可してから有効にします。
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw enable
sudo ufw status verbose
外部からのスキャン結果はまったく変わりました。
Not shown: 997 filtered tcp ports (no-response)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8081/tcp open blackice-icecap
Nmap done: 1 IP address (1 host up) scanned in 10.18 seconds
許可していないポートはすべてclosedからfilteredに変わり、全65,535ポートのフルスキャンは8.9秒から110秒になりました。スキャナーにとってはポートごとの時間が増え、サーバーについての情報は減ります。当社のUFWガイドでは、レート制限、特定IPだけの許可、IPv6を扱っています。
ただし、3行目を見てください。
5. Dockerの落とし穴
ファイアウォールが有効で、許可するルールもないのに、ポート8081は開いたままでした。このポートはdocker run -p 8081:80 nginx:alpineで起動したコンテナのものです。Dockerはカーネルのファイアウォールに独自のルールを追加し、それはufwのルールより先に評価されます。当社のサーバーに書き込まれたルールはこれです。
-A DOCKER ! -i docker0 -p tcp --dport 8081 -j DNAT --to-destination 172.17.0.2:80
ポート8081宛てのパケットは、ufwが判断する前にすべてコンテナへ転送されます。-pやports:の指定があるコンテナはすべて同じで、ssはそれを正直に0.0.0.0:8081のdocker-proxyとして表示します。
対策はlocalhostにだけ公開することです。
docker rm -f web
docker run -d --name web -p 127.0.0.1:8081:80 nginx:alpine
Composeではports:に"127.0.0.1:8081:80"と書きます。変更後、ssは127.0.0.1:8081を示し、Dockerのルールには-d 127.0.0.1/32が加わり、外部からのスキャンは8081/tcp filteredを報告しました。公開側にはnginxをリバースプロキシとしてHTTPS付きで前に置き、サーバーで開いているポートを22、80、443だけにしましょう。
変更のたびに確認すること
sudo ss -tulpn:0.0.0.0や[::]の行は、どれも公開するつもりのものですか?- ノートPCから
nmap サーバーのIP:開いているポートは一致していますか? - 公開アドレス上のデータベース、キャッシュ、管理画面:今日中に
127.0.0.1かファイアウォールの内側へ移しましょう。 - Dockerの
ports:の各指定:公開Webポート以外は127.0.0.1:で始まっていますか? - SSHが全員に開いている:fail2banか鍵だけのログインを追加しましょう。
Windows Serverでの同じ確認はGet-NetTCPConnection -State Listenで、ルールはWindowsファイアウォールにあります。$3のNano以上のすべてのLinuxプランには専用IPv4アドレス付きの版があり、そこではssに見えるものが、ファイアウォールで止めたもの以外、そのまま世界から見えます。
コメント
まだコメントはありません。最初になりましょう。