
凌晨两点被服务器告警吵醒登录上去一看/var/log/auth.log里全是同一类消息Failed password for invalid user admin from 185.220.xxx.xxx port 51234 ssh2 Failed password for root from 45.155.xxx.xxx port 40218 ssh2几分钟刷了几百条。这不是个例只要你的 SSH 端口暴露在公网上全球的扫描器、肉鸡、破解脚本就会像定时闹钟一样轮番问候。暴力破解 SSH 是服务器上最常见、最聒噪的安全威胁好在处理方案非常成熟先会看日志再会封 IP最后把 SSH 本身加固到让攻击者破解不动。这篇就把排查、封禁、加固的完整流程拆开讲一遍Ubuntu、CentOS、群晖、Docker 里的机器都能照着做。1. 为什么你的 SSH 正在被全世界轮番试探1.1 暴力破解是怎么发生的SSH 默认监听 22 端口攻击者的思路从来都是广撒网重点捕捞。他们先扫描全网开放 22 端口的 IP然后把扫描结果扔给字典工具用常见的用户名和密码组合批量尝试登录。尝试方式有两种一种是固定 IP 换用户名今天试root、明天试admin、后天试ubuntu另一种是针对某一个用户名换密码把admin123、Pssw0rd、123456这类弱口令从头到尾试一遍。工具也很普及hydra、medusa、自写脚本随便一个新手都能拿来用完全不需要技术含量。只要你的密码属于弱密码、或者还在用密码认证、又或者 root 账号直接允许远程登录中招只是时间问题。别觉得我的密码够复杂攻击是 7×24 小时持续的一天几十万次尝试再复杂的密码也可能在某次撞库泄露后被利用更别说很多服务器还有一堆默认账号和服务账号。1.2 攻击来源与特征观察从日志上看攻击来源 IP 通常集中在几个区域云厂商的海外机房、被入侵后的“肉鸡”机器、扫描器自带的代理池。来源地五花八门有美国、俄罗斯、韩国、日本也有国内的一些 IDC 段。对防御者来说重要的不是研究“谁在打我”而是把“敲门声”转成自动防御动作发现异常尝试次数自动封禁来源 IP。这个思路从手工操作到 fail2ban 自动化都是一脉相承的。还要注意一点不要只看失败登录日志更要看有没有成功的Accepted。如果某个时间点突然出现一条Accepted password for root from ...说明服务器已经被拿下了这时候封 IP 是次要的先查后门、查进程、查定时任务、查有没有挖矿程序才是正事。2. 先抓现行从日志里锁定攻击 IP2.1 日志文件在哪里看不同 Linux 发行版记录 SSH 登录日志的位置不一样这是新手最容易卡住的地方。Debian/Ubuntu 系在/var/log/auth.logCentOS/RHEL 系在/var/log/secure还有一部分新系统用 systemd-journald需要靠journalctl来查。下表可以直接对照着找系统环境日志来源查询方式Ubuntu / Debian/var/log/auth.logtail -f /var/log/auth.logCentOS / RHEL / Rocky/var/log/securetail -f /var/log/securesystemd 托管环境journaldjournalctl -u sshd群晖 DSM日志中心 / 系统日志直接在 UI 里看Docker 容器容器内日志或宿主机 journaldocker logs 容器名如果看完还不知道日志在哪直接跑一条journalctl -u ssh --since today大部分 systemd 环境都能查到。2.2 三条高价值命令快速定位攻击源第一步统计哪些 IP 在反复尝试登录把尝试次数倒序排出来grep Failed password /var/log/auth.log | awk {print $(NF-3)} | sort | uniq -c | sort -nr | head -20CentOS 的/var/log/secure格式略有差异把路径换成/var/log/secure即可必要时先用tail -n 5 /var/log/secure看一条样例再调整字段位置。我的经验是只要某个 IP 出现次数超过 20 次基本可以断定是扫描器或爆破脚本。第二步看哪些用户名被高频尝试grep Failed password /var/log/auth.log | awk {for(i1;iNF;i) if($ifor) print $(i1)} | sort | uniq -c | sort -nr | head -20如果出现大量invalid user xxx说明扫描器在枚举用户名如果集中在root说明在针对 root 爆破。这两种情况都要处理。第三步检查有没有成功登录的可疑记录grep Accepted /var/log/auth.log | tail -50正常情况下服务器的成功登录记录很少如果看到一堆来自陌生 IP 的Accepted别犹豫说明已经被入侵了立刻断网排查。2.3 看懂一行 SSH 日志的含义日志里面有一些字段值得逐字拆解比如Feb 11 03:14:22 myhost sshd[24567]: Failed password for invalid user admin from 185.220.xxx.xxx port 51234 ssh2Feb 11 03:14:22是时间myhost是主机名sshd[24567]是 sshd 进程和 PID这一行说明有人通过 SSH 协议、从185.220.xxx.xxx的 51234 端口尝试用admin这个不存在的用户登录密码错误。这里的invalid user表示系统里根本没有这个账号纯粹是扫描器在试用户名。如果看到Failed password for root from ...那是在直接爆破 root。2.4 用 lastb 查失败登录记录Linux 自带的lastb命令可以读取/var/log/btmp查看所有失败的登录尝试lastb -n 50 lastb -i | awk {print $3} | sort | uniq -c | sort -nr | head这条命令适合用来快速评估“这台机器有多招人恨”数字越大越说明有必要立刻上自动封禁。3. 手工封禁最快见效的止损动作3.1 用防火墙立即封掉攻击 IP如果攻击 IP 不多或者想先处理正在持续爆破的那几个手工封是最快的。Ubuntu 上用 ufwsudo ufw deny from 185.220.xxx.xxxCentOS 7/8 上用 firewalldsudo firewall-cmd --permanent --add-rich-rulerule source address185.220.xxx.xxx drop sudo firewall-cmd --reload底层用 iptables 的话sudo iptables -I INPUT -s 185.220.xxx.xxx -j DROP封完用ping或再观察日志攻击流量立刻断掉。但这里有个致命问题手工封禁治标不治本。扫描器的 IP 池很大你封掉一个下一分钟它换一个 IP 接着来你总不能半夜守着终端一条条敲命令。这也是我反复强调要上自动工具的原因。3.2 hosts.deny 方案为什么越来越不靠谱老运维偶尔会提/etc/hosts.deny配合 TCP Wrapper 来限制 SSH 来源sshd: 185.220.xxx.xxx这个方案本身没问题但很多新版 Linux 发行版的 sshd 默认不再编译libwrap支持hosts.deny对 sshd 是无效的写了也白写。可以先测试一下sudo ldd $(which sshd) | grep libwrap如果有输出说明还支持如果没有就别在这上面浪费时间了。与其用这种过时方案不如直接上防火墙规则。3.3 一个自动封禁的简单脚本思路在没有 fail2ban 的情况下也可以先用 shell 脚本实现“扫描日志、统计次数、超过阈值就封 IP”的简化逻辑。核心思路是定时读取日志中的Failed password行。用awk统计每个 IP 出现次数。超过阈值就调用 ufw/iptables 封禁。这个思路是对的但它有竞态问题脚本本身也要处理日志轮转、重复封禁、解封逻辑维护成本不低。所以我一般建议脚本只作为过渡方案最终还是要交给 fail2ban 这类成熟工具。4. fail2ban 实战自动封禁与参数调优4.1 fail2ban 的整体逻辑fail2ban 的架构很清晰jail定义“监控什么服务、达到什么条件、执行什么动作”filter定义“从日志里匹配哪些行”action定义“实际执行什么封禁命令”。一句话概括它把日志里的“敲门声”转成防火墙规则达到阈值后自动拉黑 IP到期自动解封。它适合所有能输出日志的服务SSH 只是最经典的一个。安装很简单# Ubuntu / Debian sudo apt update sudo apt install -y fail2ban # CentOS / Rocky sudo yum install -y fail2ban安装后不要直接改主配置/etc/fail2ban/jail.conf这个文件升级时会被覆盖。推荐在/etc/fail2ban/jail.local里写自己的配置主配置作为默认值兜底。4.2 jail.local 配置逐项解析一个经过实践检验的 SSH 防护配置长这样[DEFAULT] # 白名单把公司出口IP、跳板机IP加进去宁缺毋滥 ignoreip 127.0.0.1/8 192.168.1.0/24 # 观察窗口10 分钟内 findtime 600 # 窗口内失败次数 maxretry 5 # 封禁时长1 小时 bantime 3600 # 封禁方式一般不用动 banaction iptables-multiport [sshd] enabled true port ssh filter sshd logpath /var/log/auth.log配置项的含义是在 10 分钟findtime内同一个 IP 失败登录次数达到 5 次maxretry就封禁 1 小时bantime。这三个参数的选择有讲究。maxretry设太小容易误封比如你自己输错一次密码就被拉黑设太大又给攻击者留了太多试错机会5 次是比较平衡的值。bantime我习惯先给 3600跑一段观察误封情况稳定后再调大。如果攻击特别猛还可以把封禁时长做成阶梯式递增。网上有现成的递进配置思路是第一次封 1 小时第二次封 1 天第三次封 1 周配合 recidive jail 实现。这里不展开写配置但对公网业务服务器来说阶梯封禁是刚需。如果你用的是 CentOS/Rockylogpath要改成/var/log/secure。如果是群晖先安装群晖的 fail2ban 套件再把logpath指向日志中心导出的 SSH 日志文件。4.3 启动与操作命令sudo systemctl enable fail2ban sudo systemctl start fail2ban sudo systemctl status fail2ban查看 SSH jail 的封禁情况sudo fail2ban-client status sshd输出里会显示 Currently banned当前被封的 IP 数量和 Banned IP list封禁 IP 列表一目了然。手动解封误封的 IP 用这条sudo fail2ban-client set sshd unbanip 185.220.xxx.xxx手动封一个 IP 可以这样sudo fail2ban-client set sshd banip 45.155.xxx.xxx改完配置后重启生效sudo systemctl restart fail2ban4.4 怎么确认 fail2ban 真的在干活很多人配置完 fail2ban 后心里没底不知道有没有生效。两个方法一个用fail2ban-client status sshd看封禁清单另一个直接查防火墙规则sudo iptables -L -n | head -30 sudo firewall-cmd --direct --get-all-rules看到类似REJECT all -- 185.220.xxx.xxx 0.0.0.0/0的规则说明封禁已经写入内核防火墙了。我遇到过不少“fail2ban 显示已封禁但攻击还在”的情况查到最后都是防火墙规则没生效原因可以看第 6 章的常见问题部分。4.5 参数调优的几点经验ignoreip一定要加自己的固定 IP。否则你自己在外面改服务器配置时输错几次密码就被自己的防御系统扔进小黑屋重启服务器才能出来非常尴尬。公网业务和有固定办公网段的团队可以把bantime默认 3600 改成 86400。暴力扫描器的行为是持续性的封 1 小时过后它还会回来封 1 天能大幅减少日志噪音。如果攻击者换 IP 很快说明是动态代理池单纯依赖 fail2ban 会有些吃力。这时maxretry可以降到 3同时配合后面的“禁用密码登录”一起用。5. 从堵到防让 SSH 暴力破解彻底失去意义5.1 密钥登录暴力破解的天敌封 IP 只是治标真正治本的一招是把密码认证关掉改用 SSH 密钥登录。密钥是一对公钥和私钥公钥放在服务器上私钥留在本地登录时用私钥签名验证。没有私钥的人连“试密码”的机会都没有。这一步做完暴力破解基本就对你无效了。生成密钥对ssh-keygen -t ed25519 -C your_comment一路回车会在~/.ssh/下生成id_ed25519私钥和id_ed25519.pub公钥。把公钥放到服务器上ssh-copy-id -i ~/.ssh/id_ed25519.pub useryour_server_ip然后登录测试ssh useryour_server_ip如果能免密登录就可以修改服务端配置了。编辑/etc/ssh/sshd_config找到这几行改掉PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes改完重启 sshdsudo systemctl restart sshd这里必须提醒在确认密钥登录完全正常之前千万不要急着关密码认证。曾经有人改完配置忘了测试直接断开连接结果密钥没配好密码认证又关了只能通过应急管理后台或 VNC 登录折腾了一整晚。稳妥的顺序是生成密钥 → 上传公钥 → 测试密钥登录 → 确认无误 → 关闭密码认证。5.2 限制登录入口的细节参数密钥之外sshd_config里还有几个参数对降低风险特别有用PermitRootLogin no禁止 root 直接用 SSH 登录。攻击者最喜欢爆破 root因为 root 一旦进去就是最高权限。禁止 root 远程登录后攻击者还要先猜一个普通用户名难度直线上升。MaxAuthTries 3限制单次连接的最大认证尝试次数。默认值是 6改成 3 可以压缩爆破效率配合 fail2ban 更快触发封禁。LoginGraceTime 30连接建立后 30 秒内必须完成认证超时断开。防止攻击者用大量连接占用资源属于防资源耗尽的细节。AllowUsers admin ops只允许指定用户通过 SSH 登录。这个参数对单用户服务器尤其好用直接过滤掉大部分无效用户名。改完同样要测试再退出。5.3 换端口到底值不值把 SSH 端口从 22 改成 2222 或者其他高位端口确实能显著减少扫描量。互联网上大部分扫描器只扫默认端口改了端口之后很多“习惯性爆破”的脚本就不再光顾了。但这不是银弹真正的定向攻击不看端口扫描全端口段的工具也不少而且改端口会带来一些连锁问题云平台安全组要同步放行新端口。防火墙入方向规则要改。你的 SSH 工具要配置新端口VSCode、Xshell、FinalShell 等都要手动改配置文件或连接参数。公司网络策略如果只放行 22 端口还得找网络管理员协调。我的建议是不折腾端口也行只要你上了密钥登录和 fail2ban22 端口暴露着问题也不大。如果精力允许换端口叠加密钥登录当然更好属于“一眼看过去很干净”的体验。5.4 Docker、群晖、麒麟等环境的差异与注意点很多人的 SSH 并不直接跑在物理机系统上而是跑在容器、NAS 或国产系统里这些环境下的日志路径和封禁方式有差异。Docker 容器如果 SSH 服务跑在容器里面容器日志和宿主机日志是分开的。fail2ban 直接监控宿主机/var/log/auth.log往往看不到容器内 sshd 的日志。比较常见的做法是让容器把日志打到宿主机或者在宿主机上用docker logs配合 fail2ban 的自定义 filter。更省事的方案是让 SSH 服务跑在宿主机容器只暴露业务端口这样 SSH 防护不需要考虑日志透传。另外网上经常看到有人在群晖的 Docker 里跑 Ubuntu 容器当开发机不影响 SSH 防护逻辑就多一步日志路径确认。群晖 DSM群晖带日志中心界面里能看到 SSH 登录记录。它的 fail2ban 包是套件中心安装的独立版本配置完成后会在防火墙里生成规则。群晖的 SSH 一般也是默认 22 端口老版本还存在 admin 账户爆破风险不低建议直接关闭 admin 账户用普通用户配密钥登录。麒麟/UOS 这类国产服务器系统底层大多兼容 systemd日志查询用journalctl -u sshd即可fail2ban 基本能直接用。要注意的是某些版本预装了安全软件可能自带 SSH 防护策略先和服务商或安全软件厂商确认避免多个封禁策略互相冲突。两端都配置时规则叠加会导致同一 IP 被封方式不一致排查起来头大。5.5 别忘了其他入口加固 SSH 的同时务必检查其他远程管理入口。很多人把 SSH 加固到天衣无缝结果服务器还开着 Web 面板、Telnet、数据库远程端口照样被突破。Telnet 是明文协议数据包里直接能看到密码务必关掉。MySQL/Redis 这类服务如果不需要公网访问监听地址改成127.0.0.1或者用防火墙把 3306、6379 等端口限制在内网。云平台的安全组也要同步收紧只放行必要的端口。6. 常见问题与排查实录6.1 日志里根本没有 Failed password是怎么回事有一种情况攻击者用的是pam模块之外的认证方式或者 sshd 日志级别调低了导致失败尝试没有写入 auth.log。可以先把 sshd 的日志级别调到 VERBOSE在/etc/ssh/sshd_config里确认LogLevel VERBOSE然后重启 sshd。还有一个更常见的原因日志轮转太快刚好把爆破记录切走了。用grep -r Failed password /var/log/auth.log*把所有历史日志都扫一遍往往能找到答案。6.2 fail2ban 显示封禁了但攻击还在继续这个问题优先排查三件事第一检查 fail2ban 用的logpath和实际日志路径是否一致Ubuntu 的 fail2ban 默认读/var/log/auth.logCentOS 上不改成/var/log/secure它就什么都监控不到。第二检查防火墙是否真的生效iptables -L -n里没有对应规则那就是 action 执行出问题了。第三检查攻击是不是来自 IPv6。很多服务器同时开启了 IPv6fail2ban 默认的 action 只处理 IPv4攻击者从 IPv6 来源打过来就绕过了封禁。解决方法是给 sshd 设置AddressFamily inet强制走 IPv4或者给 fail2ban 写 IPv6 的 action。6.3 把自己误封了怎么解人在外面手头突然断连ssh 一登录就被拉黑这是很多人第一次配置 fail2ban 时踩过的坑。如果还留着另一条管理通道云平台 VNC、面板登录上去跑fail2ban-client set sshd unbanip 你的IP就行。如果没有其他通道只能等封禁到期或者去云平台控制台操作防火墙放行。所以ignoreip一定要提前配好尤其是家庭宽带和公司固定 IP 都要写进去。6.4 攻击 IP 来自 IPv6 怎么封日志里出现from 2404:6800:xxxx:xxxx::1这种地址就是 IPv6 攻击。先检查系统有没有给 sshd 开放 IPv6 监听如果不需要 IPv6 访问直接在/etc/ssh/sshd_config里设置AddressFamily inet重启后 SSH 只监听 IPv4IPv6 爆破直接无效。如果需要 IPv6就配置 fail2ban 的action iptables-multiport之外再启用 IPv6 支持或者用ip6tables手工封禁sudo ip6tables -I INPUT -s 2404:6800:xxxx::1/128 -j DROP6.5 重启服务器后封禁规则丢失手工iptables -I加的规则重启后不会自动保存。fail2ban 管理的规则一般没问题因为它会在服务启动时重新加回来。如果你手工加过一大批 IP建议写成一个脚本或者用iptables-save /etc/iptables.rules保存并在 systemd 里加一条 Restart 执行规则恢复。最省心的还是让所有封禁都走 fail2ban由它统一管理规则会自动恢复。6.6 常见问题速查表问题现象最可能的原因解决方法fail2ban 不封任何 IPlogpath 和实际日志路径不一致核对 /var/log/auth.log 或 /var/log/secure日志有 Failed 但 fail2ban 不动作filter 不匹配当前日志格式用 fail2ban-regex 测试 filter封了还继续被连攻击来自 IPv6设置 AddressFamily inet 或启用 IPv6 封禁自己登录不了ignoreip 没配好用 VNC 进入后解封并加白名单重启后手工封禁失效iptables 规则未持久化用 iptables-save 保存规则或统一走 fail2ban7. 最后再分享一点实战心得如果只让我留三条建议我会说立刻装 fail2ban尽快切密钥登录千万别留着 root 密码远程登录。这套组合下来暴力破解基本只会留下一些日志噪音不再构成实质威胁。甚至对日志噪音也可以用 fail2ban 的 bantime 调大来减少比如把首次封禁设为 1 天配合阶梯封禁能安静很多。现实里我见过太多人把精力花在研究攻击者来自哪里、封了多少 IP却一直拖着不做密钥登录。的确封 IP 的过程很有成就感但真正的安全是“即使攻击者敲一万次门也进不来”。配置一次密钥登录和 fail2ban 之后你会发现自己很长一段时间都不会再因为 SSH 被爆破这件事熬夜了。这个踏实感值得你花半小时换。