读懂 SSH 认证日志:每一行失败记录的含义

各发行版把失败的 SSH 登录记在哪里、如何解读不同的消息形态,以及如何区分打错密码的用户和正在跑清单的僵尸网络。

阅读约 8 分钟

日志到底在哪里

这是第一个绊人的地方,因为它取决于发行版。Debian 和 Ubuntu 把认证事件写进 /var/log/auth.log。RHEL、CentOS、AlmaLinux、Rocky 和 Fedora 用 /var/log/secure。完全转向 journald 的系统可能这两个文件都没有,一切都在日志系统里。

可移植的答案是 journalctl,只要有 systemd 就能用:

bash
# 可移植:实时跟读 sshd 自己的日志
journalctl -u ssh -f          # Debian/Ubuntu 的单元名
journalctl -u sshd -f         # RHEL/Fedora 的单元名

# 只看最近 24 小时的失败
journalctl -u ssh --since -24h | grep -E 'Failed|Invalid user'

# 在仍然写文件的发行版上:
grep -E 'Failed|Invalid user' /var/log/auth.log     # Debian/Ubuntu
grep -E 'Failed|Invalid user' /var/log/secure       # RHEL 系

消息形态逐一解读

sshd 发出的失败消息只有一小组,每一种都有确切含义。记住下面这五种,几乎覆盖您会看到的一切:

  • Failed password for <user> from <ip> port <n> ssh2 —— 账户存在,密码错误。要么是真实用户输错了,要么是机器人猜中了一个有效用户名。
  • Failed password for invalid user <user> from <ip> —— 账户不存在。几乎总是攻击:您自己的用户知道自己的用户名。通常还会伴随一条单独的「Invalid user」记录。
  • Connection closed by authenticating user <user> <ip> port <n> [preauth] —— 客户端在认证中途放弃。常见于只想抓取 banner 的扫描器,以及拒绝密码尝试的纯密钥服务器。
  • Disconnected from authenticating user root <ip> port <n> [preauth] —— 在禁止 root 的服务器上发生的 root 登录尝试。这里量大是暴露主机上最常见的特征。
  • Maximum authentication attempts exceeded for <user> from <ip> —— 客户端在一次连接内触达了 MaxAuthTries。往往是提供了很多密钥的 agent,但若来自陌生地址,那就是在轮换凭据的机器人。

区分攻击与手误

有三个信号可以区分它们,在自动处置之前,三个都该看到。

速率与持续性。人会在一分钟内尝试两三次,然后要么进去了,要么打电话给您。机器人会连续数小时保持稳定速率,通宵不歇,从不停顿。

账户多样性。人只会尝试一个账户:自己的。机器人顺着清单走,而其中大多数账户并不存在——正是上面提到的「invalid user」特征。

来源。您的同事从少数几个已知地址连入。攻击来自托管商网段,每几百次尝试就换一个地址,而且常常在同一小时内来自多个国家。

下面这条命令按来源地址对最近一天的失败记录分组,并显示每个地址尝试过哪些用户名,三个信号一次可见:

bash
journalctl -u ssh --since -24h --no-pager |
  grep -oE 'Failed password for (invalid user )?[^ ]+ from [0-9.]+' |
  awk '{ ip = $NF; user = $(NF-2); count[ip]++; users[ip] = users[ip] " " user }
       END { for (i in count) printf "%6d  %-16s %s\n", count[i], i, users[i] }' |
  sort -rn | head -15

真正该告警的那一行

登录失败是噪音。有一条消息不是,而且它值得一条告警,而不是一块仪表盘:

Accepted password for <user> from <ip> —— 一次成功的密码登录。如果您的服务器本应只用密钥,这行就意味着它并不是,您有配置问题。如果允许密码,那么在同一网段的一串失败之后、从陌生地址出现的这一行,就是您最不想看到的序列。

值得一并关注的还有:带着您不认识的密钥指纹的 Accepted publickey,以及任何本不该有 sudo 权限的用户开启的 sudo 会话。这两者告警成本都很低,而重要性远超拒绝记录的数量。

bash
# 最近一周所有成功登录,含方式与来源
journalctl -u ssh --since -7d --no-pager |
  grep -E 'Accepted (password|publickey|keyboard-interactive)'

从读取到阻断

手工读日志是诊断,不是防御——等您跑这条命令的时候,攻击已经持续一周了。真正能改变什么的,是持续不断地做这件事,并把阈值转化为防火墙规则。

最朴素的做法是跟读日志、按来源统计失败次数,并调用 nft 把地址加入阻断集合。它能跑起来,然后就撞上每个这类脚本都会撞上的问题:集合无限膨胀;高负载下日志轮转,事件丢失;升级之后单元悄无声息地死掉;一次误匹配把管理员挡在唯一入口之外。

SSH Protector 把这个循环作为服务来运行:一个合并的 nftables 集合、永远优先于封禁的白名单、开机时恢复的封禁,以及在本地做出的判断——因此网络扛不住的时候它仍然扛得住。它读取的正是上面展示的那条流,没有任何东西对您隐藏。

FAQ

Linux 上失败的 SSH 登录记录在哪里?
在 Debian 和 Ubuntu 上是 /var/log/auth.log。在 RHEL、CentOS、AlmaLinux、Rocky 和 Fedora 上是 /var/log/secure。在完全依赖 journald 的系统上这两个文件都不存在,改为读取日志系统:journalctl -u ssh(或 -u sshd,取决于发行版的单元名),这在任何地方都适用。
「Failed password for invalid user」是什么意思?
表示有人尝试用一个本机上并不存在的账户进行认证。您自己的用户知道自己的用户名,所以这行几乎总是攻击——机器人在跑 root、admin、ubuntu、test、git、oracle 之类的常见名字清单。单看数量并不值得惊慌:那是任何暴露主机上的背景水平。
为什么我会看到数千次 root 登录尝试?
因为 root 是所有攻击清单上的第一个用户名,而机器人无从得知它在您的服务器上被禁止。把 PermitRootLogin 设为 no 或 prohibit-password 后它们无法成功,但也永不停止,因为一次尝试对它们毫无成本。要降低这个量,靠的是封禁来源,而不是登录策略。
怎么查看成功的 SSH 登录?
在同一份日志里搜索「Accepted」——Accepted publickey、Accepted password 或 Accepted keyboard-interactive,后面跟着用户名、来源地址和端口。这才是值得告警的那一行:在本应只用密钥的服务器上出现成功的密码登录是配置错误,而在一串失败之后来自陌生地址的成功登录则是安全事件。

不必再手工翻日志

代理会持续监视这条完全相同的流,并把它转化为 nftables 规则。一台服务器永久免费,无需信用卡。