服务器被 SSH 爆破之后,我搭了一套登录审计:从 auth.log 统计到每日基线打卡

个人站被爆破之后,我才开始认真做 SSH 登录审计

去年有段时间,我的小 VPS 上 SSH 登录日志里突然冒出大量失败记录,来自世界各地,一秒钟好几条。一开始我以为是配置错了,后来才反应过来——这就是最典型的 SSH 暴力破解。从那天起,我把「登录审计」当成服务器安全的基础工程来做了一遍。这篇文章把我踩过的坑和最终成型的方案完整记下来,适合所有用密码或者密钥登录 Linux 服务器的个人站长。

第一步:先看懂 SSH 日志到底记录了什么

很多人连自己的服务器被人试了多少次都不知道,因为根本不知道日志在哪。现代 Debian/Ubuntu 系统上,SSH 日志通常走 systemd-journald,也可能单独落到 /var/log/auth.log。先确认你的日志流向:

ls -l /var/log/auth.log
journalctl -u ssh -n 20 --no-pager

典型的失败登录长这样:

Failed password for invalid user admin from 45.xx.xx.xx port 51234 ssh2
Failed password for root from 103.xx.xx.xx port 40001 ssh2

注意第一行的 invalid user——说明对方用的是系统里根本不存在的用户名在盲试。这类流量占了绝大多数,说明攻击者是全自动扫描器,拿一个用户名字典撞所有 SSH 端口。

登录成功的记录则是:

Accepted publickey for root from 1.2.3.4 port 22000 ssh2: RSA SHA256:...

这条一定要盯紧,因为它意味着有人真的进来了。如果你在日志里看到来源 IP 不认识的 Accepted,那基本可以判定服务器已经被入侵。

第二步:快速统计攻击规模

光靠眼睛翻日志效率太低,几万行根本翻不完。直接上命令做聚合。统计失败登录来源 IP 的排行:

grep "Failed password" /var/log/auth.log | \
  awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -20

如果日志走 journald,则用:

journalctl -u ssh --since "24 hours ago" | \
  grep "Failed password" | awk '{print $(NF-3)}' | \
  sort | uniq -c | sort -rn | head -20

再来看看攻击者都在猜哪些用户名:

grep "Invalid user" /var/log/auth.log | \
  awk '{print $8}' | sort | uniq -c | sort -rn | head -20

你大概率会看到 admin、test、oracle、postgres、ubuntu 这一串。有意思的是:如果你的服务器 SSH 根本没有开放给公网,或者关了密码登录,这些尝试全部会失败在第一步。审计的意义就在于用数据确认自己的防线到底有没有挡住。

第三步:给 SSH 做最小化的暴露面收敛

审计只能看,真正降风险还是要改配置。打开 /etc/ssh/sshd_config,按下面几项逐条对照。改完一定要先 sshd -t 验证,再重启服务,否则可能把自己锁在外面。

关闭密码登录,只留密钥

PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no

这一条是收益最高的改动。关掉密码登录之后,全自动扫描器能做的所有用户名密码碰撞全部失效,你的 SSH 攻击面瞬间小一个数量级。前提是你已经配置好并测试过密钥登录——务必在断开当前连接前,另开一个终端验证密钥能登进去。

禁止 root 直接登录

PermitRootLogin prohibit-password

注意不要简单写 no,因为很多自动化脚本、Ansible 都靠 root 的密钥登录。写成 prohibit-password 表示 root 只能用密钥、不能用密码,兼顾安全和自动化便利。

限制重试次数和登录宽限期

MaxAuthTries 3
LoginGraceTime 20

默认 MaxAuthTries 是 6,LoginGraceTime 是 120 秒。改成 3 和 20,能让每次爆破尝试的代价大幅上升——暴力破解本质上拼的是「单位时间内能试多少次」,这两个参数直接把速率压下去。

换掉默认 22 端口(注意:不是安全,是降噪)

把 SSH 端口从 22 换成比如 2222,有经验的站长都知道这不是真正的安全措施,因为端口扫描一样能找到你。但它的实际价值在于把那些只扫 22 端口的无脑脚本全部过滤掉,日志一下子清爽了。真正要防的是有目标的攻击者,那还是要靠密钥认证。

第四步:把审计做成自动化

手工敲命令只能应急,长期要靠脚本。下面这个思路是我现在在用的:每天凌晨跑一次,把过去 24 小时的失败登录统计出来,超过阈值就发一封邮件或者写进一个日报文件。

#!/bin/bash
# /usr/local/bin/ssh-audit.sh
LOG="/var/log/auth.log"
REPORT="/var/log/ssh-audit-$(date +%F).txt"
{
  echo "=== SSH 审计报告 $(date) ==="
  echo "--- 24h 内失败登录次数 ---"
  journalctl -u ssh --since "24 hours ago" | grep -c "Failed password"
  echo "--- 失败来源 TOP10 ---"
  journalctl -u ssh --since "24 hours ago" | \
    grep "Failed password" | awk '{print $(NF-3)}' | \
    sort | uniq -c | sort -rn | head -10
  echo "--- 成功登录记录 ---"
  journalctl -u ssh --since "24 hours ago" | grep "Accepted"
} > "$REPORT" 2>&1

用 cron 每天跑一次:

0 4 * * * /usr/local/bin/ssh-audit.sh

这样你每天早上只要扫一眼报告,就能知道昨天有没有异常。真正有价值的不是「挡住攻击」这个结果——密码关掉之后本来就挡得住——而是通过持续记录建立一个基线,任何偏离基线的行为(比如突然出现一个 Accepted,或者失败次数暴涨十倍)都能立刻被你看到。

第六步:把 SSH 审计做成「登录基线」

审计做久了会发现,光看绝对数字意义不大——有的服务器一天失败三千次,有的只有十次,都可能完全正常。真正有用的是跟自己比:建立一份正常状态下的基线,然后只关注偏离基线的异常。

建议每天记录三个数字:失败登录总数、唯一来源 IP 数、成功登录次数。连续记录两周之后,你就能画出一条属于自己服务器的曲线。之后任何一天的数字只要明显跳出这条曲线的范围,就值得点进去看看。比如某天唯一来源 IP 数突然从 20 涨到 400,说明有新的扫描平台盯上了你;某天成功登录次数从每天的 3 次(你自己)变成 4 次,那第 4 次是谁登的,必须查清楚。

下面这段脚本把三个数字写进一个 CSV,方便长期积累:

#!/bin/bash
CSV="/var/log/ssh-baseline.csv"
DAY=$(date +%F)
FAIL=$(journalctl -u ssh --since "24 hours ago" | grep -c "Failed password")
UNIQ=$(journalctl -u ssh --since "24 hours ago" | \
  grep "Failed password" | awk '{print $(NF-3)}' | sort -u | wc -l)
OK=$(journalctl -u ssh --since "24 hours ago" | grep -c "Accepted")
[ -f "$CSV" ] || echo "date,failed,unique_ips,accepted" > "$CSV"
echo "$DAY,$FAIL,$UNIQ,$OK" >> "$CSV"

积累一个月之后,用 awk 做个最简单的同比:awk -F, 'NR>1{print $1, $2, $4}' /var/log/ssh-baseline.csv,一眼就能看出哪天异常。这种「轻量、长期、可回溯」的审计,比装一堆花哨的监控面板更实用——因为个人站长真正缺的不是工具,而是持续观察的习惯和数据本身。

第七步:密钥管理本身就是审计的一部分

关掉密码登录之后,你的安全就完全押在密钥上了。所以密钥的管理必须比密码更严格,否则等于把锁换了但钥匙随便扔。几条实践原则:

  • 私钥永远不离开本地。~/.ssh/id_ed25519 不要上传到任何服务器、网盘、聊天工具。需要从多台机器登录,就生成多份密钥分别授权,而不是复制同一份私钥。
  • 给密钥加口令(passphrase)。用 ssh-keygen -t ed25519 -a 100 生成时设置口令,配合 ssh-agent 使用,日常体验几乎无感,但私钥就算泄露也无法直接被使用。
  • 定期审计 authorized_keys。cat ~/.ssh/authorized_keys 看看每一条后面的注释,确认每一条都对应一个你认识且还在用的设备。离职的同事、卖掉或废弃的旧笔记本对应的公钥必须删掉。
  • 用 ssh-keygen -l 核对指纹。给公钥加注释容易抄错,用 ssh-keygen -lf ~/.ssh/authorized_keys 列出每条的指纹和长度,确认没有多出来的奇怪条目——这正是入侵者留后门最常用的位置。

把 authorized_keys 也纳入每日审计范围,是很多人忽略的一环。攻击者一旦通过某个漏洞拿到写权限,第一件事往往就是往 authorized_keys 里塞自己的公钥,从此拥有长期免密访问。你如果从不检查这个文件,可能几个月都发现不了。

第八步:发现异常之后怎么办

审计的终点是响应。如果日志显示某个 IP 在疯狂尝试,先别急着封——万一那是你自己或者同事的动态 IP 呢。确认是攻击后,临时封禁最直接:

iptables -I INPUT -s 45.xx.xx.xx -j DROP

但手工封禁是打地鼠,长期方案是上 fail2ban 这类工具,让它自动读日志、自动封 IP。不过要注意:fail2ban 依赖日志格式解析,如果你的 SSH 换了端口或者日志走了 journald,要选对应的 filter。另外别把封禁时间设得太短,否则对方换个 IP 又来。

更进一步的思路是反向利用:与其被动挨打,不如把 22 端口直接关掉,只在需要管理时通过 Tailscale 之类的私有网络接入,公网根本看不到你有一个 SSH 服务。这个方案我在另一篇组网文章里详细写过,有兴趣可以翻一翻。

一份可以照着做的检查清单

  1. 确认 SSH 日志位置(auth.log 还是 journald)。
  2. 统计过去 24 小时失败登录次数与来源 IP。
  3. 检查日志中是否有非本人的 Accepted 记录(这条最重要)。
  4. sshd_config 确认 PasswordAuthentication no、PermitRootLogin prohibit-password。
  5. MaxAuthTries 调到 3,LoginGraceTime 调到 20。
  6. 把审计脚本放进 cron 每天跑,固定输出报告。
  7. 对持续攻击的 IP 用 fail2ban 自动封禁,而不是手工 iptables。

服务器安全不是装一个防火墙就完事的,它更像是一套持续运转的观察 + 收敛机制。把审计做起来,你至少能做到「知道正在发生什么」,而绝大多数被拖库、被挖矿的服务器,问题恰恰出在主人根本不知道发生了什么事。

Last modification:October 5th, 2026 at 01:23 pm

Leave a Comment