开放端口的清单,就是通往你服务器的路径清单。其余的一切——防火墙、WAF、入侵检测——都建在它之上。正因如此,「这里有什么在监听」在任何一次审计里都排在最前面,也正因如此,这项检查最常给出令人不快的答案:查出来的东西里有一半不是你打开的,而是某个软件包的安装脚本打开的。
读懂输出
sudo ss -tulpn
各个标志:t 是 TCP,u 是 UDP,l 只看监听中的,p 是进程,n 让数字保持为数字。没有 sudo 时进程那一列是空的,整个练习也就失去了意义。
要紧的是 Local Address:Port 这一列,而那里的区别是根本性的:
127.0.0.1:3306——服务只能从本机够到。这是好事;0.0.0.0:3306——从任何 IPv4 地址,也就是从互联网。这正是需要检查的;[::]:3306——IPv6 的同样情况。单独的一行,而且经常被漏看;203.0.113.25:443——在某个具体地址上,通常是有意为之。
然后对每一行带 0.0.0.0 或 [::] 的记录问一个问题:陌生人是否应当能够到这个东西。对 80 和 443 答案是肯定的。对几乎其余的一切答案都是否定的。
常见的发现
Redis,端口 6379。那里最危险的一行。Redis 默认不要求密码,而它的命令允许往磁盘写文件——也就是往 authorized_keys 里塞一把别人的密钥。从 Redis 出现在公网地址到被利用,中间是几个小时,有时更短。请检查配置里的 bind 127.0.0.1 和 protected-mode yes。
Memcached,11211/UDP。即便里面没有什么值钱的东西,你的服务器也会变成别人攻击的放大器——而投诉会从你的服务商那里来。
MySQL 和 PostgreSQL,3306 与 5432。密码是有的,但对它的猜测持续不断,而数据库版本的更新频率又低于人们希望的水平。它们几乎从来不需要朝外:应用就在同一台机器上,而你自己的工作用一条 SSH 隧道就够了。
Elasticsearch 9200、MongoDB 27017。历史上默认是不带认证的。它们的公网实例是数据泄露新闻的常备来源。
Docker API,2375。开放的 Docker 控制端口就等于宿主机上完全不需要密码的 root。它通常在尝试远程访问 Docker 之后出现。
控制面板与 phpMyAdmin,各自的端口:8080、8083、10000。倒不是绝对不能开,但它们恰恰是把大部分尝试都招过来的东西。
要在服务上修,而不是在防火墙上
想用一条 UFW 规则把每个发现都堵上,这可以理解,但那是第二道防线而不是第一道。规则可能被误删,防火墙在排障时可能被临时关掉,而 Docker 更是完全绕过 UFW 去发布端口。服务自身配置里的绑定设置能挺过这一切:
- MySQL/MariaDB——
bind-address = 127.0.0.1; - PostgreSQL——
listen_addresses = 'localhost'; - Redis——
bind 127.0.0.1 ::1; - Docker Compose——按
"127.0.0.1:5432:5432"的形式发布。
防火墙叠在上面作为保险,而不是取代这一步。
不清楚时怎么找到那个进程
ss 给出名字和 pid。然后:
sudo systemctl status <pid>
sudo lsof -i :8080
第一条命令会说明这个进程属于哪个 systemd 单元——通常足以判断它是什么、是否需要。一个在高端口上监听、且从系统目录之外启动的陌生进程——比方说从 /tmp 或 /dev/shm——就已经不再是配置问题,而是另起一次调查的理由。
从外部检查是强制的
ss 回答的是「什么在监听」,而不是「什么够得着」。这两者之间隔着防火墙、NAT 和服务商自己的规则。诚实的答案只能靠从另一台机器扫描得到:
nmap -Pn -p- 203.0.113.25
nmap -Pn -p- -6 2001:db8::1
别跳过第二条命令:几乎每台 VPS 都有 IPv6 地址,它的规则是单独写的,而服务是同时在协议的两个版本上监听的。
这不是一次性的检查
开放端口的清单会自己变化。你装了一个包,它带来了一个服务并打开了一个端口。你更新了面板,它把默认值恢复了。你启动了一个容器,它绕过防火墙发布了端口。一次性的检查只对今天有效,仅此而已。
价值不在清单本身而在它的变化:一个昨天还没有的新端口,是一个简短却信息量很大的信号。正该这样去盯着它——作为带历史的快照,而不是凭记忆想起来的 ss 输出。下面的演示页面展示的正是这个。