服务器SSH暴力破解如何防范?密钥认证、登录限制与日志审计

随便找一台开着 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 参数作用速查

参数默认值建议值作用说明
PasswordAuthenticationyesno关闭密码认证,是防爆破的核心
PermitRootLoginyes(部分发行版 prohibit-password)no禁止 root 直接登录,保留审计与追溯能力
MaxAuthTries63单次连接最多尝试次数,降低单次连接的爆破效率
LoginGraceTime120(秒)30未登录成功的连接最长保持时间,防止连接占用
MaxSessions105单个 TCP 连接上可开的会话数上限
ClientAliveInterval0(不探测)300空闲连接心跳间隔,配合下一项自动断开僵尸会话
ClientAliveCountMax32心跳无响应次数上限,达到即断开连接
PermitEmptyPasswordsnono禁止空口令账号登录

四、登录限制与自动封禁

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 加固实施清单

  1. 建立具备 sudo 权限的普通账号,验证它能正常登录并提权,这是禁 root 的前提。
  2. 部署密钥:客户端生成 Ed25519 密钥,ssh-copy-id 上传,服务器端修正 .ssh 权限为 700、authorized_keys 为 600。
  3. 保留一个现有会话不动,新开一个窗口验证密钥登录成功,再继续下一步。
  4. 修改 sshd_config:PermitRootLogin no、PasswordAuthentication no、MaxAuthTries 3、LoginGraceTime 30,可选项加上 AllowUsers。
  5. sshd -t 校验后 systemctl reload sshd,不要用 restart。
  6. 部署 fail2ban,配置 sshd jail(maxretry=3、findtime=600、bantime=3600),并把办公出口 IP 写入 ignoreip。
  7. 配置 auditd 规则监控 sshd_config 与 authorized_keys,规则写入 /etc/audit/rules.d/ 保证持久化。
  8. 建立日报与告警,让"有没有异常登录"这个问题每天都有答案,而不是等出事再查。
  9. 长期收敛:定期用 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 监控并定期检查内容。
  • 不要只配一次就不管:账号会新增、公钥会追加、配置会被改,审计要变成常态化的每日动作,而不是事件驱动的一次性检查。