EQVPS
控制台开始使用

如何检查 VPS 上开放的端口:什么在监听、互联网能看到什么,以及 Docker 的意外

查看服务器监听的每个端口,找到背后的进程,像攻击者那样从外部扫描服务器,并关掉不需要的。还包括防火墙开启后依然敞开的 Docker 端口。每个数字都在一台全新的 Ubuntu 24.04 服务器上实测。

开着又被遗忘的端口,是进入服务器的经典途径:公网地址上的数据库、调试面板、发布了谁也不想发布的端口的容器。只要知道两条命令和一个陷阱,检查只需一分钟。下面的一切都是在用我们镜像创建的全新 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。

每次改动后的检查清单

  1. sudo ss -tulpn:每一行 0.0.0.0 或 [::] 都是你有意开放的吗?
  2. 从笔记本运行 nmap 服务器IP:开放的端口对得上吗?
  3. 公网地址上的数据库、缓存或管理面板:今天就把它挪到 127.0.0.1 或防火墙后面。
  4. 每个 Docker ports: 配置:如果不是公网 Web 端口,是否以 127.0.0.1: 开头?
  5. SSH 对所有人开放:加上 fail2ban 或仅密钥登录。

在 Windows Server 上,同样的检查是 Get-NetTCPConnection -State Listen,规则在 Windows 防火墙里。从 $3 的 Nano 起,每个 Linux 套餐都有带独立 IPv4 地址的版本,在那里你在 ss 里看到的,除了防火墙拦下的,全世界都能看到。

常见问题

新 VPS 上开放了哪些端口?

在我们的 Ubuntu 24.04 镜像上只有一个:22 端口的 SSH。除此之外只有 127.0.0.53 和 127.0.0.54 上的本地 DNS 解析器在监听,互联网访问不到它。从另一台机器扫描全部 65,535 个 TCP 端口,恰好只发现一个开放端口。

closed 和 filtered 有什么区别?

closed 表示服务器回复那里没有任何东西在监听,所以扫描器立刻就知道。filtered 表示完全没有回应,这是防火墙丢弃数据包时的表现。两者都能挡住入侵者,但 filtered 还隐藏了有哪些服务,在我们的服务器上让完整扫描慢了大约十二倍。

为什么 ss 显示的端口,nmap 却说是 closed?

该服务只监听 127.0.0.1,也就是回环地址。服务器本机上的程序可以连接,外部谁都连不上。在 ss 中看 Local Address 列:127.0.0.1:8080 是私有的,0.0.0.0:8080 或 [::]:8080 在防火墙允许时可从互联网访问。

我开启了 ufw,为什么 Docker 端口还是开着?

Docker 会写入自己的防火墙规则,并且这些规则先于 ufw 的规则生效。用 -p 8081:80 启动的容器,在我们的服务器上即使 ufw 已启用、也没有 8081 的规则,仍能从外部访问。请改为发布到 localhost:-p 127.0.0.1:8081:80,并在公网一侧前置 nginx 或 Caddy。

怎样从自己的电脑检查一个端口?

单个端口用 nc -zv 服务器IP 443;最常见的一千个端口可安装 nmap 后运行 nmap 服务器IP。在 NAT 套餐上,互联网只能访问为你转发的 SSH 端口,其他端口按设计无法访问。

评论

暂无评论。来做第一个吧。

发表评论

评论在显示前会经过审核。