随便找一台开着 22 端口的公网服务器,查看它的 /var/log/auth.log,你大概率会看到成百上千行 "Failed password for root from ..."。这不是有人盯上了你,而是自动化的扫描程序在按 IP 段批量尝试常见用户名和弱口令——只要你的服务器在公网上,这类尝试就不会停止。真正危险的不是这些尝试本身,而是其中只要有一次成功,你失去的就是整台机器的控制权。SSH 暴力破解的防范思路非常清晰:先让密码认证彻底失效(改用密钥),再限制登录行为(次数、来源、账号),最后建立日志审计闭环(能发现问题、能追溯、能告警)。本文按这三个层次给出可落地的配置。
一、先确认:我的服务器正在被爆破吗
在动手加固之前,先看清楚现状。以下三条信息必须掌握:失败的规模、失败的来源、有没有已经成功的登录。
# Debian/Ubuntu 日志在 /var/log/auth.log,RHEL/CentOS/Rocky 在 /var/log/secure
# 统计失败密码认证的来源 IP TOP 20
grep 'Failed password' /var/log/auth.log | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort | uniq -c | sort -rn | head -20
# 统计失败总次数与成功登录次数
grep -c 'Failed password' /var/log/auth.log
grep -c 'Accepted' /var/log/auth.log
# 查看被尝试的用户名分布(如果大量出现 root、admin、test、oracle,就是典型扫描)
grep 'Failed password' /var/log/auth.log | awk '{print $9}' | sort | uniq -c | sort -rn | head -20
# systemd 环境可直接查 journal
journalctl -u sshd --since '24 hours ago' | grep -ci 'failed'
判读标准:单日失败次数在几百次以内属于互联网背景噪音,不必紧张;如果单日过万,或者同一个 IP 在一分钟内尝试几十次,说明已被定点扫描;如果日志里出现 Accepted password for root 而你并不记得有这次登录,那就要按入侵事件处理了,立即参考恶意进程的处置流程。
# 查看成功登录历史(含来源 IP 与时间)
last -n 30
last -n 30 -i
# 查看失败登录记录(依赖 /var/log/btmp)
lastb | head -30
# 查看所有账号最近一次登录时间,长期未使用却活跃的账号要警惕
lastlog
# 检查是否有新增的系统账号
awk -F: '$3 >= 1000 && $3 < 65534 {print $1, $3, $6, $7}' /etc/passwd
二、第一道防线:用密钥认证取代密码
密钥认证是防范暴力破解最根本的手段。密码可以被猜、可以被撞库、可以被撞运气;而一对 256 位 Ed25519 密钥在可预见的时间内无法被穷举。只要关闭密码认证,无论对方尝试多少次,结果都是同一个:失败。
2.1 生成并部署密钥
# 在客户端生成 Ed25519 密钥对(推荐;如需兼容老设备可用 -t rsa -b 4096) ssh-keygen -t ed25519 -C 'ops-laptop' -f ~/.ssh/id_ed25519 # 把公钥上传到服务器 ssh-copy-id -i ~/.ssh/id_ed25519.pub opsuser@203.0.113.10 # 在服务器端修正权限(权限不对 SSH 会直接拒绝使用该密钥) chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys chown -R opsuser:opsuser ~/.ssh
生成密钥时建议设置 passphrase(密钥口令)。这样即使私钥文件泄露,对方仍然无法直接使用。日常可以配合 ssh-agent 或 ssh-add 避免重复输入。
2.2 先验证、再关闭密码
这是最容易出事故的一步,务必按"先验证密钥能登录,再关闭密码认证"的顺序操作,并且保留一个已经登录的会话不要退出,确认新会话能用密钥正常进入后,再关闭密码认证。
| 认证方式 | 抗暴力破解能力 | 使用便利性 | 适用场景 |
|---|---|---|---|
| 密码认证 | 弱,完全依赖口令强度,弱口令几分钟即被攻破 | 高,任何设备都能连 | 不建议在公网服务器使用 |
| 密钥认证 | 强,无法在有效时间内穷举 | 中,需要携带私钥文件 | 所有公网服务器的标准配置 |
| 密钥 + 密钥口令 | 强,且私钥泄露后仍有第二道锁 | 中,可配合 ssh-agent 缓解 | 推荐,特别是高权限账号 |
| 密钥 + TOTP 双因子 | 很强,需同时持有私钥与动态口令 | 较低,需要额外部署 | 堡垒机、生产核心服务器 |
三、sshd_config:把登录行为收紧
OpenSSH 的主配置文件在 /etc/ssh/sshd_config(部分系统还有 /etc/ssh/sshd_config.d/*.conf,后者优先级更高)。下面是一份经过实践检验的基础配置:
# /etc/ssh/sshd_config 关键项 # 建议先保持 22,待全部加固完成、确认无误后再考虑更换 Port 22 # 关闭密码认证(先确认密钥可用再改这一项) PasswordAuthentication no PubkeyAuthentication yes PermitEmptyPasswords no # 禁止 root 直接登录,改用普通账号 + sudo 提权 PermitRootLogin no # 单次连接的认证尝试次数,默认 6,建议 3 MaxAuthTries 3 # 单个网络连接上允许的会话数,默认 10,建议 5 MaxSessions 5 # 登录宽限期,默认 120 秒,建议 30 秒 LoginGraceTime 30 # 只允许指定用户登录(也可用 AllowGroups,二选一即可) AllowUsers opsuser deploy # AllowGroups sshusers # 关闭不需要的功能,缩小攻击面 X11Forwarding no PermitUserEnvironment no AllowAgentForwarding no AllowTcpForwarding no # 空闲连接保活:300 秒探测一次,连续 2 次无响应则断开 ClientAliveInterval 300 ClientAliveCountMax 2 AuthorizedKeysFile .ssh/authorized_keys UsePAM yes
# 修改前先备份 cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%F) # 语法检查(这一步能挡住绝大多数低级错误) sshd -t # 平滑加载,不要 restart,避免断开现有连接 systemctl reload sshd # 确认服务状态与监听端口 systemctl status sshd ss -lntp | grep -E ':22\b'
这里要特别提醒 AllowUsers 的风险:一旦配置了它,名单之外的所有用户都会被拒绝,包括你以后新建的账号。如果配置时漏掉了某个正在使用的账号,那个人就会被锁在门外。因此建议先加自己常用的账号,观察几天后再逐步收敛名单。同理,PermitRootLogin no 之前必须确认有一个具备 sudo 权限的普通账号可用。
3.1 参数作用速查
| 参数 | 默认值 | 建议值 | 作用说明 |
|---|---|---|---|
| PasswordAuthentication | yes | no | 关闭密码认证,是防爆破的核心 |
| PermitRootLogin | yes(部分发行版 prohibit-password) | no | 禁止 root 直接登录,保留审计与追溯能力 |
| MaxAuthTries | 6 | 3 | 单次连接最多尝试次数,降低单次连接的爆破效率 |
| LoginGraceTime | 120(秒) | 30 | 未登录成功的连接最长保持时间,防止连接占用 |
| MaxSessions | 10 | 5 | 单个 TCP 连接上可开的会话数上限 |
| ClientAliveInterval | 0(不探测) | 300 | 空闲连接心跳间隔,配合下一项自动断开僵尸会话 |
| ClientAliveCountMax | 3 | 2 | 心跳无响应次数上限,达到即断开连接 |
| PermitEmptyPasswords | no | no | 禁止空口令账号登录 |
四、登录限制与自动封禁
4.1 fail2ban:把反复失败的 IP 自动关门外
即使关闭了密码认证,仍然建议部署 fail2ban。原因有两点:一是减少日志噪音和连接资源消耗,二是它同样适用于保护其他服务(如 Nginx 的认证接口)。
# /etc/fail2ban/jail.d/sshd.local [sshd] enabled = true port = ssh filter = sshd # Debian/Ubuntu 用 /var/log/auth.log;RHEL 系用 /var/log/secure # 若系统已全面使用 systemd journal,可改为 backend = systemd logpath = /var/log/auth.log maxretry = 3 findtime = 600 bantime = 3600 # 屡犯者封禁时间递增,最高一周 bantime.increment = true bantime.factor = 2 bantime.maxtime = 604800
# 启用并查看状态 systemctl enable --now fail2ban fail2ban-client status sshd fail2ban-client get sshd banip --with-time # 误封时手动解封 fail2ban-client set sshd unbanip 203.0.113.66 # 把自己的办公出口 IP 加入永久白名单(写入 jail 的 ignoreip) # ignoreip = 127.0.0.1/8 ::1 203.0.113.0/24
参数怎么定:maxretry = 3 配 findtime = 600 意味着 10 分钟内失败 3 次即封禁。对正常使用密钥的用户来说,几乎不可能触发(密钥认证通常一次成功);而自动化脚本会在几十秒内触发。如果你所在环境有 CI 系统频繁用密码登录,需要放宽或改成密钥登录后再收紧。
4.2 iptables 直接限制新建连接速率
作为 fail2ban 之外的补充,可以直接在系统层限制 SSH 的新建连接频率。这样即使 fail2ban 服务异常,系统层仍有一道防线。
# 记录并限制:300 秒内同一 IP 的 SSH 新建连接超过 5 个即丢弃 iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 300 --hitcount 5 --name SSH -j DROP # 限制单 IP 对 22 端口的并发连接数不超过 3 iptables -A INPUT -p tcp --dport 22 -m connlimit --connlimit-above 3 --connlimit-mask 32 -j REJECT # 查看规则命中情况,确认是否真的在拦流量 iptables -L INPUT -n -v --line-numbers
五、日志审计:能发现、能追溯、能告警
防护做得再好,也需要审计来验证。审计要解决三个问题:有没有异常登录、配置文件有没有被偷偷改过、能不能定期收到一份可读的报告。
5.1 用 auditd 监控关键文件
攻击者拿到权限后最常见的持久化手段之一,就是往 authorized_keys 里追加自己的公钥,或者修改 sshd_config 打开密码认证。用 auditd 可以监控这些文件的写入与属性变更。
# 临时规则(重启失效) auditctl -w /etc/ssh/sshd_config -p wa -k sshd_config_change auditctl -w /root/.ssh/authorized_keys -p wa -k rootkey_change auditctl -w /etc/passwd -p wa -k passwd_change auditctl -w /etc/shadow -p wa -k shadow_change # 查询审计记录(-i 把数字 uid/gid 解析成名称) ausearch -k sshd_config_change -i | tail -20 ausearch -k rootkey_change -i | tail -20 # 持久化:写入 /etc/audit/rules.d/ssh.rules,然后重启服务 # -w /etc/ssh/sshd_config -p wa -k sshd_config_change systemctl restart auditd auditctl -l
5.2 每日登录审计脚本
#!/bin/bash
# /usr/local/bin/ssh-audit.sh 每日 SSH 登录审计,可放入 crontab
LOG=/var/log/auth.log
echo "===== SSH 登录审计 $(date '+%F %T') ====="
echo
echo "--- 失败来源 TOP10 ---"
grep 'Failed password' "$LOG" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort | uniq -c | sort -rn | head -10
echo
echo "--- 今日成功登录 ---"
grep 'Accepted' "$LOG" | tail -20
echo
echo "--- 非密钥方式登录(应为空,出现即为异常)---"
grep 'Accepted password' "$LOG"
echo
echo "--- fail2ban 当前封禁 ---"
fail2ban-client status sshd 2>/dev/null | tail -6
echo
echo "--- 具备登录权限的系统账号 ---"
awk -F: '$7 !~ /(nologin|false)/ {print $1, $3, $7}' /etc/passwd
# 加入 crontab,每天 8 点执行并把报告发给管理员 0 8 * * * /usr/local/bin/ssh-audit.sh | mail -s 'SSH 登录日报' ops@example.com # 也可以只在异常时告警:失败次数超过 500 就发一次 0 * * * * c=$(grep -c 'Failed password' /var/log/auth.log); [ "$c" -gt 500 ] && echo "$c" | mail -s 'SSH 爆破告警' ops@example.com
六、SSH 加固实施清单
- 建立具备 sudo 权限的普通账号,验证它能正常登录并提权,这是禁 root 的前提。
- 部署密钥:客户端生成 Ed25519 密钥,
ssh-copy-id上传,服务器端修正.ssh权限为 700、authorized_keys为 600。 - 保留一个现有会话不动,新开一个窗口验证密钥登录成功,再继续下一步。
- 修改 sshd_config:
PermitRootLogin no、PasswordAuthentication no、MaxAuthTries 3、LoginGraceTime 30,可选项加上AllowUsers。 sshd -t校验后systemctl reload sshd,不要用 restart。- 部署 fail2ban,配置 sshd jail(
maxretry=3、findtime=600、bantime=3600),并把办公出口 IP 写入ignoreip。 - 配置 auditd 规则监控 sshd_config 与 authorized_keys,规则写入
/etc/audit/rules.d/保证持久化。 - 建立日报与告警,让"有没有异常登录"这个问题每天都有答案,而不是等出事再查。
- 长期收敛:定期用
lastlog清理不再使用的账号,检查authorized_keys里是否有陌生的公钥。
小结
防范 SSH 暴力破解的完整链条是"让爆破无效 → 让爆破变慢 → 让行为可见"。第一层靠密钥认证:把 PasswordAuthentication 设为 no,攻击者的所有尝试都变成徒劳;第二层靠 MaxAuthTries、LoginGraceTime、iptables 的 recent 限速和 fail2ban 的动态封禁,降低尝试频率并快速切断来源;第三层靠 last、lastb、auditd 与每日审计脚本,让你随时能回答"谁在什么时候从哪登录过"。三层都做到位,SSH 就不再是你的短板。
避坑建议
- 最大禁忌:先关密码认证再验证密钥。顺序反了且没有保留活动会话,你就会被永久锁在门外,只能去控制台 VNC 救场。
- 不要直接 restart sshd:用
sshd -t校验 +systemctl reload sshd,restart 会断开所有现有连接。 - 不要忘了 fail2ban 的 ignoreip:CI 系统、办公网络、监控探针如果不加白名单,很容易被批量封禁,表现为"大家都突然连不上了"。
- 不要迷信改端口:把 SSH 改到 2222 只能减少日志量,不能提供实质保护,而且会让你误以为已经安全。
- 不要忽略 authorized_keys 的审计:这是最常见的持久化和后门位置,必须纳入 auditd 监控并定期检查内容。
- 不要只配一次就不管:账号会新增、公钥会追加、配置会被改,审计要变成常态化的每日动作,而不是事件驱动的一次性检查。
企业QQ咨询




