開著又被遺忘的連接埠,是進入伺服器的經典途徑:公開位址上的資料庫、除錯面板、發布了誰也不想發布之連接埠的容器。只要知道兩個指令和一個陷阱,檢查只需一分鐘。下面的一切都是在用我們映像建立的全新 Ubuntu 24.04 伺服器上完成,並從第二台機器掃描,所以輸出就是你在自己的伺服器上會看到的樣子。
1. 什麼在監聽
指令是 ss。各個字母分別代表 TCP、UDP、監聽中的通訊端、數字連接埠和程序名稱:
sudo ss -tulpn
在新伺服器上它輸出六行:
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:…(特定位址) | 任何能到達該位址的人 |
所以新伺服器對外只暴露一樣東西:22 埠的 SSH,IPv4 和 IPv6 都有。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 執行的 worker,都在 *:80,涵蓋 IPv4 和 IPv6。fuser 只輸出 PID,適合在指令碼中使用。如果需要釋放連接埠,請停止服務而不是殺掉 PID,否則 systemd 會重新啟動它:sudo systemctl stop nginx。
3. 網際網路看得到什麼
ss 呈現的是伺服器自己的視角。網際網路的視角可能不同,因為中間有防火牆,或服務綁定在 127.0.0.1 上。要看到真實情況,請從另一台機器掃描伺服器,例如你的筆電或第二台伺服器。工具是 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,000 個連接埠,第二種檢查全部 65,535 個,第三種檢查指定清單。在我們的新伺服器上、開啟防火牆之前:
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 秒,找到的仍是那唯一一個連接埠。關閉的連接埠會立即以 reset 回應,所以掃描很快。
有三種狀態值得注意:
- open:有程式接受了連線。這就是你的攻擊面。
- closed:伺服器回覆那裡沒有東西在監聽。安全,但它確認了伺服器存在,以及它沒有執行什麼。
- filtered:沒有回應。防火牆丟棄了封包。掃描器無法判斷背後是否有服務。
不安裝任何東西檢查單一連接埠,可用 nc -zv 伺服器IP 443,它回傳 succeeded 或 refused,遇到被過濾的連接埠會一直等到逾時。檢查 UDP 要為 nmap 加上 -sU,並做好慢的準備;在我們的伺服器上,三個常見 UDP 連接埠 53、123 和 161 都是 closed。
關於 NAT 方案的說明:從網際網路只能到達我們為你轉發的 SSH 連接埠。掃描共用位址看到的是我們的網路邊緣,而不是你的伺服器,其他連接埠無論上面監聽著什麼都無法存取。在擁有獨立 IPv4 位址的方案上,每個開放連接埠都由你自己管理。
4. 關閉不需要的
三種方法,從最好到涵蓋面最廣。
綁定到 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。
但請看第三行。
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。在公開的一側前置帶 HTTPS 的 nginx 反向代理,讓伺服器上開放的連接埠只剩 22、80 和 443。
每次變更後的檢查清單
sudo ss -tulpn:每一行0.0.0.0或[::]都是你刻意開放的嗎?- 從筆電執行
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 裡看到的,除了防火牆攔下的,全世界都看得到。
評論
目前還沒有評論。歡迎成為第一位。