面向公网的 Linux 服务器 SSH 加固清单
一份为必须从公网接受 SSH 的 Linux 服务器准备的有序清单:先做什么、大多数清单错在哪里,以及什么可以安心押后。
如何使用这份清单
加固清单通常因为同一个原因而失效:它们按主题排列,而不是按风险排列。人们从上往下做,时间在 banner 配置附近耗尽,却从未碰到认证。
这一份是有序的。第 1 节消除 Linux 服务器真正被拿下的途径。如果您只有一个小时,做第 1 节然后停下——它比下面所有内容加起来都更有价值。
清单假设服务器必须接受来自任意地址的 SSH。如果您的不必,第 3 节会收缩成一条防火墙规则,而这是好消息。
1. 认证
失守始于有效凭据的情况,远多于始于未打补丁的守护进程。这一节就是全部要害。
- 彻底关闭密码认证:PasswordAuthentication no。这是本文档中价值最高的一行——没有密码可猜,猜解攻击就根本不可能成功。
- 同时设置 KbdInteractiveAuthentication no。漏了它,PAM 仍会提供密码提示,于是密码照常可用,您什么都没改。请用 sshd -T 验证,而不是读配置文件。
- 把 PermitRootLogin 设为 prohibit-password,若 root 从不直接连接则设为 no。root 是所有攻击清单上的第一个名字。
- 用口令保护私钥,并且不要放在共享机器上。泄露的密钥和泄露的密码一样糟糕,而且泄露途径也相同:被偷的笔记本、误提交、CI runner 环境。
- 有人离职时轮换密钥,并审计每个账户的 ~/.ssh/authorized_keys。旧密钥会悄悄堆积,而且从来没有人清理。
- 用显式清单设置 AllowUsers 或 AllowGroups。即便是只用密钥,这也能防止新建的服务账户被意外地变成可远程访问。
2. 减少可被访问的面
在「谁能登录」之后,第二个问题是「还有什么在监听」。每一个开放端口都是此刻正被人测试的服务。
- 清点真正在监听的东西:在主机上执行 ss -tlnp,然后从另一个网络验证其中哪些会应答。这两份清单很少一致。
- 把不需要远程访问的服务绑定到 localhost——尤其是数据库。监听在 0.0.0.0 上的 PostgreSQL 或 Redis,是比 SSH 严重得多的问题。
- 运行默认拒绝的防火墙:nftables 或发行版的前端,只放行您明确列出的东西。
- 能限制 SSH 来源地址就限制。如果合法地址集合小而稳定,这是成本最低的真正措施——而在有人出差的那一刻它就不再管用。
- 可以考虑从 22 端口迁走以降低噪音,但要明白那是降噪,不是防护。
3. 自动封禁登录失败
只用密钥消除了「被猜中」的风险,但它不消除流量,而这一节讲的正是流量。
机器人不知道您的配置。它们继续连接、提交密码、被拒绝——每一次尝试消耗一个 TCP 连接、一次 sshd 分叉、一行日志和一小片 CPU。放任不管,一台暴露的服务器每天要吸收数千次,而日志轮转得足够快,足以丢掉您真正需要的事件。
fail2ban 是标准答案,也是合理的起点。请把它配置好——bantime -1、在 ignoreip 中写上自己的地址,并监控封禁数量而不是服务状态,因为它的失效方式是沉默。它的结构性边界是:面对轮换网段的僵尸网络只封单个地址;状态挺不过重建;您的各台服务器之间没有共享知识。
SSH Protector 以托管代理的形式做同一件事:子网封禁、永久且开机恢复、从 sshd 配置中读出真实的 SSH 端口、安装时就把您的地址加入白名单、在所有受保护服务器之间共享信誉,以及一个能回答它是否还在运行的控制台。
4. sshd 配置细节
值得设置:单看都是小事,合起来是更小的攻击面。每次改动后运行 sshd -t,并使用 reload 而不是 restart。
- MaxAuthTries 3 —— 每次连接允许的凭据尝试更少,机器人要完成同样多的猜测就需要更多连接。
- 调低 MaxSessions 和 MaxStartups —— 限制同时在途的未认证连接数量,而这正是洪水攻击消耗的东西。
- LoginGraceTime 30 —— 一条未认证的连接不该开着两分钟。
- 除非确有需要,否则 X11Forwarding no 和 AllowAgentForwarding no。特别是 agent 转发,会让被入侵的目标端能够使用您的密钥。
- 只保留现代算法:移除过时的加密算法、MAC 和密钥交换方法。当前发行版的默认值是合理的,所以在复制十年前写的加固片段之前请先核对——好几个流行片段如今造成的破坏已多于修复。
5. 日志与检测
您无法调查没有记录下来的东西,而默认配置保留的比您预期的少。
- 确认 sshd 的 LogLevel 至少为 INFO。设为 QUIET 时它不记录任何认证失败。
- 调高 journald 的保留时长,或把日志送出主机。被入侵机器的本地日志是攻击者可以编辑的证据;别处的副本则不是。
- 针对有意义的事情告警,而不是针对数量:来自陌生地址的成功登录、本应只用密钥的服务器上出现 Accepted password、新增的 sudoers 条目、新增的 authorized_keys 条目。
- 偶尔而有意识地回顾成功登录记录。失败是噪音;一次您无法解释的成功,才是全部要害。
6. 补丁与恢复
最后两项,它们之间的先后顺序,远不如「两项都存在」重要。
保持 sshd 和内核处于最新——如果您的变更流程允许,请启用无人值守安全更新。OpenSSH 出现过认证前即可利用的漏洞,将来还会有,而从披露到大规模利用的窗口如今以天计。
然后是备份,其中有一个属性比其他所有属性都重要:至少保留一份服务器自身既够不着也删不掉的副本。勒索软件运营者会在加密任何东西之前先寻找备份目标,而一个被入侵主机可以写入的挂载点不是备份,它是等着被销毁的第二份副本。并且要真的恢复一次——从未被恢复过的备份是一个假设,不是一个计划。
FAQ
- SSH 加固中最重要的一步是什么?
- 关闭密码认证。设置 PasswordAuthentication no 和 KbdInteractiveAuthentication no 之后,没有密码可猜,整类凭据猜解攻击就从「更难」变成了「不可能」。就投入产出而言,加固清单上没有别的条目能与之相比。
- 加固时该不该改 SSH 端口?
- 为降噪值得做,为防护则不然。从 22 迁走通常能让登录失败量下降 90% 以上,并让日志重新变得可读,但 SSH 会在版本 banner 中自报家门,任何全范围扫描器都能立刻找到它。请把它当作日志卫生,而绝不是一项控制措施。
- fail2ban 算是正规 SSH 加固的一部分吗?
- 它是合理的基础层,也远好过什么都没有。请把 bantime 设为 -1,把自己的地址写进 ignoreip,并监控封禁数量而不是服务状态——一个因发行版升级导致过滤器不再匹配的 jail,看起来和太平的一周毫无区别。
- MaxAuthTries 能防住暴力破解吗?
- 只有很有限的作用。它限制的是单次连接内的凭据尝试次数,于是攻击者只需多开几条连接;这略微抬高了对方成本,却不改变结果。设为 3 值得做,但它不是防御——真正降低流量的是在防火墙上封禁来源。
