跳转到内容

改掉 22 端口就安全了吗? ​

公网机器的 sshd 日志里堆着几千条失败登录,这件事几乎不是针对你。绝大多数是背景噪声:扫描器按 IP 段顺序敲门,抡一份通用字典试 root、admin、ubuntu、postgres 这些名字。真正值得处理的不是「怎么让这些行消失」,而是先分清你遇到的是哪一类攻击,再看你打算装的措施能不能挡住对应那一类。

先给一张骨架表,后面逐条展开:

你面对的情况在日志里长什么样主要靠什么挡
无差别扫描用户名全是 root、admin、ubuntu 这类字典词,来源 IP 几乎不重复能挡的不多。只认密钥、fail2ban 降噪、换端口压低日志噪声
复用泄露凭据出现你真实存在的用户名,来源集中在少数 IP 或同一网段只认密钥。密码复杂度这一层基本救不了
已经拿到密钥或会话的人只有 Accepted publickey,没有失败尝试加固挡不住,要的是密钥管理、审计和最小权限

先分清你在防什么 ​

日志里的用户名分布,是判断威胁类型最省事的线索。root、admin、ubuntu、postgres、docker、deploy、git 这些词来自通用字典,扫的人并不认识你,只是按 IP 段顺序敲门。出现你的真实账号名、公司名或者内部服务名,含义就完全不同了:对方手上有一份跟你相关的用户名列表,通常来自别处泄露的数据库,这时候才值得单独处理。

看一眼分布就够了,不需要通读日志:

bash
journalctl -u ssh --since -24h | grep -oE 'Failed password for (invalid user )?[^ ]+' | sort | uniq -c | sort -rn | head

顺带纠正一个常见叫法:这事不是「撞库」。撞库是拿别处泄露的账号密码去撞另一个服务,赌你在两边用了同一套凭据;SSH 上的无差别爆破是猜通用字典,连你的用户名都不知道。两者在日志里是能分辨的,分辨点就是上面那条:用户名是不是你的真实账号。

改端口:改变的是日志信噪比,不是攻破难度 ​

换端口挡得住的是只扫 22 和 2222 这类常见端口的廉价脚本;对 IPv4 地址空间做全端口扫描本身就是常规操作,所以「换个隐蔽端口就找不到我」不成立。它的真实收益在日常运维:日志干净了,异常一眼能看出来,多台机器上尤其明显。

容易被忽略的是代价,因为改端口不是改一个文件,而是一次全链路变更:云安全组(这一层在实例之外,本机防火墙管不到)、本机防火墙、SFTP 与备份脚本、CI 的部署任务、监控探针、Git 的 remote,还有同事书签里的 ssh -p。任何一处没跟上,表现都是连不上,而且这种连不上和被墙、和 sshd 挂了长得一模一样。

所以把它当成降噪措施,不要把它算进安全边界。

只认密钥:这才是边界,但密钥本身是新的攻击面 ​

密码登录和密钥登录的差别不是「哪个更难猜」,而是机制不同。密码是共享秘密,任何人都可以在线无限次尝试;密钥登录是服务器发一个挑战,你只有用私钥完成签名才能通过,猜是猜不出来的,只能偷。

这就是为什么只认密钥能挡掉前两类威胁的绝大多数:字典没有用,泄露的密码也没有用。它挡不住的是第三类——拿到私钥的人,在服务端看来和你本人没有区别。

麻烦也随之转移。密钥登录把风险从「猜密码」搬到了「私钥保管」:私钥副本散落在几台机器上、CI 里躺着一把没有口令的私钥、跳板机上留着别人的 key、agent forwarding 把本地密钥借给了中间那台机器、几年前的密钥用的还是老算法。这些不会出现在失败登录日志里,因为攻击者走的是正门。密钥集中在哪里、哪把还在用、什么时候轮换,见本地加密与交互式 OTP 认证里对应的部分。

fail2ban:抬高的是成本,不是墙 ​

fail2ban 的机制很直白:读日志或 journald,按规则匹配失败尝试,匹配够了就往防火墙里插一条封禁规则。所以它能工作有三个前提——日志读得到、防火墙动得了、封的 IP 不会伤到自己人。

它的真实作用是抬高「按次尝试」的成本:密码登录还开着的时候,这个作用最明显;换成只认密钥之后,它更多是在压噪声。它防不住的东西也很明确:低频分布式尝试(每个 IP 只试一两次,跨几千个来源),以及任何拿着密钥来敲门的人。

误封比漏封更常见:你自己的出口 IP、家里的宽带、公司的 CGNAT 出口(一个 IP 后面是一整栋楼)、监控探针、跳板机。白名单要按「谁需要连这台机器」写全,而不是只写「我的办公 IP」。

bash
sudo fail2ban-client status sshd

四条红线,以及它们为什么是这个顺序 ​

这四条的正文不是命令,是顺序。顺序错了,代价是整台机器进不去,而不是某条命令报错。

先放行新通道,再关旧通道。 云安全组在实例之外,和本机防火墙是两层。先把新端口在两层都放行、用一条全新的连接验证能登录,之后才允许关掉 22。反过来做,症状是整台机器失联,连 sshd 的报错都看不到。

先验证密钥,再禁用密码。 重载配置之后新策略立刻生效,当前那个还开着的会话能继续用,但它不代表你下次还进得来。禁用密码登录之前,必须用一个全新连接确认密钥能登录成功。

看生效值,不看写进去的那行。 这是最容易被忽略的一条。sshd_config 对同一个关键字采用「第一个出现的值生效」,而 drop-in 目录通常被 Include 在文件最前面,所以 drop-in 里的值会压过你写在下面的那行——你以为改了,其实没生效。sshd -T 给的是合并之后的结果,只看这个:

bash
sudo sshd -T | grep -E '^(port|passwordauthentication|permitrootlogin)'

举个真实的例子:一台机器上这三行的输出是 port 22、passwordauthentication no、permitrootlogin yes。密码登录确实关了,但 root 用密钥照样能直接登进来——这个组合是不是你想要的,得自己判断。

先确认救生舱,再动手。 云控制台、VNC、串口控制台这些入口绕开 sshd,不依赖你能连上 SSH。加固的每一步都是在这条唯一的通道上做手术,先确认另一条通道可用,再动刀。

现在这活儿该交给谁 ​

这四步现在已经很少是手敲的了,多半是你说需求、AI 出方案并执行。这改变了风险的位置:敲错一个字符的概率下降了,但「顺序对不对」「生效值是不是你以为的那个」「锁死了还能不能回来」这三件事不会因为执行者是 AI 就消失,而 AI 不会因为可能把你锁在门外而停下来问你。

所以交给它的时候,值得写进去的不是命令清单,而是顺序、验证点和退路:

text
帮我给这台服务器做 SSH 加固,按这个顺序来,每步做完先给我看结果再继续:
1) 先在云安全组和本机防火墙放行新端口,验证一条全新连接能登录,之后才允许关闭 22;
2) 再部署密钥,用全新连接验证密钥登录成功,然后才禁用密码登录,过程中不要断开我当前的会话;
3) 最后装 fail2ban 盯 SSH 失败登录,白名单里加上我的出口 IP 和监控探针的出口;
4) 每一步用 sshd -T 给我看合并后的生效值,并保留修改前的配置备份和回滚办法。

几个常被误解的说法 ​

日志里几千次失败登录,说明我被盯上了? 看用户名。全是 root、admin 这类字典词,就是无差别扫描;出现你的真实账号名或内部服务名,才说明对方手上有跟你相关的数据。

换个端口,扫描就找不到了? 全端口扫描是常规操作。换端口的收益在日志信噪比,不在「找不到」。

禁用密码登录后,日志就干净了? 认证失败的行会消失,但连接照样被敲,剩下的多是连上又断开这种无结论的记录。攻击面从「猜密码」转到了「偷密钥」。

装了 fail2ban 就等于封住了? 它管的是同一来源的重复尝试。低频分布式尝试和拿着密钥来的人都不受影响,而误封自己人倒是常有的事。

把 IP 加进白名单就安全了? 白名单是可用性设计,不是安全设计。你出差、开热点、走代理,出口 IP 就变了;白名单里的网段一旦被很多人共用,等于给陌生人留门。

加固做完就可以不管了? 需要的是可复查:改过什么、现在生效的是什么、还有哪些密钥有效、封禁列表有没有误伤自己人。

加固之后要留下的东西 ​

能连上不等于能管好。做完之后,值得留下这几样:修改前的配置备份和回滚路径;一份「当前生效值」的记录;封禁与白名单的可见性(谁被封了、为什么、有没有封到自己人);还有一份密钥清单——哪把在哪台机器上、还在不在用、什么时候该轮换。

多台机器一起做的时候,难点会从「怎么改」变成「哪些机器改过了、现在还是不是那个状态」,这类对照是运维工作台擅长的部分,和服务器侧的机制是两回事。像 Termark 这样的 SSH 工作台负责把你送到机器上、把主机与密钥放在一处对照,sshd、fail2ban 该管的生命周期仍然由它们自己管。分清这条边界,出问题的时候才知道该查连接、查封禁,还是查配置。

参考资料 ​

[1] OpenSSH sshd_config 手册

[2] OpenSSH ssh 手册

[3] fail2ban 文档

[4] Ubuntu Server 文档:OpenSSH

[5] NIST SP 800-63B:认证器与口令

相关阅读 ​

Termark · SSH 客户端与终端工作台