SSH 异常登录调查与安全加固实录
概述
2026-06-02,腾讯云安全告警提示服务器存在异常登录行为。Hermes Agent 对此进行了系统性的安全审计:从日志分析、IP 溯源、密钥追溯、到最终实施安全加固(禁用密码登录 + 部署 fail2ban)。调查结论:服务器未被入侵,告警源于持续的 SSH 暴力破解扫描,唯一一次"陌生 IP 成功登录"实为自有 Kali VM 的合法连接。
1. 事件起因
腾讯云安全产品检测到大量 SSH 登录失败记录,触发异常登录告警。用户要求 Hermes 查明:是谁登录了服务器,做了什么操作。
2. 调查过程与命令分析
以下按调查逻辑链逐条说明每条命令的用途和发现。
2.1 成功登录记录:last
last -20
为什么用这个命令: last 读取 /var/log/wtmp(二进制日志),按时间倒序列出所有成功的登录会话。参数 -20 限制最近 20 条。
WTMP 机制: 每当用户登录/登出,login 和 sshd 会调用 pam_lastlog 将登录会话信息写入 /var/run/utmp(当前在线)和 /var/log/wtmp(历史记录)。这是一个二进制文件,普通文本工具读不了,必须用 last 解析。
发现:
ops从121.35.xx.xx(深圳,后来确认为公司 Kali VM)登录 — 合法ops从120.229.xx.xx(深圳,后来确认为家里物理机)登录 — 合法root从orcaterm(腾讯云网页终端)登录 — 合法- 没有不明用户的成功登录
2.2 失败登录记录:lastb
sudo lastb -20
为什么用这个命令: lastb 是 last 的对称命令,读取 /var/log/btmp(记录失败的登录尝试)。失败尝试不会出现在 last 的输出中,需要用 lastb 专门查看。
BTMP 文件格式: 与 WTMP 完全相同的二进制格式(struct utmp),只是记录登录失败的事件。这也是为什么 lastb 不需要新语法——它只是更换了数据源文件。
发现(核心线索):
es ssh:notty 68.183.204.19 Tue Jun 2 14:02
es ssh:notty 47.76.26.151 Tue Jun 2 13:15
ubuntu ssh:notty 59.110.9.189 Tue Jun 2 12:59
root ssh:notty 80.94.95.115 Tue Jun 2 12:01
docker ssh:notty 109.244.69.102 Tue Jun 2 11:52
openhabi ssh:notty 47.93.150.183 Tue Jun 2 07:25
...
- 来自全球各地的大量 IP 在尝试常见的用户名(root、ubuntu、docker、es、user、openhabian)
- 这是典型的 SSH 暴力破解扫描(brute-force scanning)
- 但从
lastb看不出是否有人成功——需要交叉验证auth.log
2.3 认证日志详细分析:/var/log/auth.log
sudo cat /var/log/auth.log | tail -100
然后针对性过滤:
sudo cat /var/log/auth.log | grep -E "Failed password|Invalid user|authentication failure"
为什么用 auth.log: 这是 SSH 认证的详细文本日志,由 rsyslog 根据 /etc/rsyslog.conf 中的 auth,authpriv.* 规则写入。每条记录包含时间戳、主机名、进程名、PID 和消息内容。/var/log/wtmp 只记录"谁成功登录了",但 auth.log 记录了认证的全过程——包括使用的认证方法、密钥指纹、失败原因。
关键发现 — 可疑的成功登录:
2026-06-02T14:10:09 Accepted publickey for ops from 81.128.xx.xx port 43064
ssh2: ED25519 SHA256:R17uR2Hy2v5YRIRyxrzoiXOApHjdb/Si8eGqfv/vpD4
- IP
81.128.xx.xx(英国波士顿)成功登录了ops用户 - 会话仅持续 2 秒(14:10:09 登录 → 14:10:11 断开)
- 6 秒后又重复一次(14:10:15 → 14:10:17)
- 使用了
ops-vm-wiki密钥
然后过滤确认没有被密码成功登录的:
sudo cat /var/log/auth.log | grep "Accepted" | grep -v "121.35.xx.xx\|120.229.xx.xx\|orcaterm"
只找到 81.128.xx.xx。这说明没有其他来源成功登录。
2.4 失败登录统计
sudo lastb | wc -l # 总失败次数
sudo lastb | awk '{print $3}' | sort | uniq -c | sort -rn | head -20 # 按 IP 聚合
结果: 总共 4,490 次失败登录,前两名各尝试了 2,200+ 次:
| IP | 尝试次数 |
|---|---|
| 51.91.64.198 | 2,233 |
| 51.222.47.156 | 2,225 |
2.5 IP 地理定位
curl -s "http://ip-api.com/json/81.128.xx.xx"
curl -s "http://ip-api.com/json/121.35.xx.xx"
curl -s "http://ip-api.com/json/120.229.xx.xx"
为什么用 ip-api.com: 这是一个免费的 IP 地理位置 API,返回 JSON,可以快速了解 IP 所属国家、城市、ISP。虽然精度有限(可能误差到城市级别),但对于判断"是不是自己国家的 IP"已经足够。
结果:
| IP | 位置 | ISP |
|---|---|---|
| 81.128.xx.xx | 英国波士顿 | British Telecom |
| 121.35.xx.xx | 深圳广东 | Chinanet(电信) |
| 120.229.xx.xx | 深圳广东 | China Mobile(移动) |
2.6 SSH 密钥指纹追溯
for f in ~/.ssh/authorized_keys; do
while read -r line; do
echo "$line" | ssh-keygen -lf /dev/stdin 2>/dev/null
done < "$f"
done
为什么用这个命令: ssh-keygen -lf 读取公钥文件并计算其指纹(SHA256 hash)。通过比对 auth.log 中记录的指纹 SHA256:R17uR2Hy2v5YRIRyxrzoiXOApHjdb/Si8eGqfv/vpD4,可以确定是哪一把密钥被英国 IP 使用。
SSH 指纹原理: SSH 指纹是对公钥的 SHA256 哈希,长度固定(32 字节 Base64 编码 = 43 字符),比完整的公钥(数百字符)更紧凑。ssh-keygen -lf 输出的指纹格式与 auth.log 记录的完全一致,可以直接字符串匹配。
结果: 匹配到 ops-vm-wiki 密钥。
2.7 服务器端触发排查
sudo journalctl -u cron --since "2026-06-02 14:08" --until "2026-06-02 14:12"
为什么查 cron: 如果 14:10 的连接是服务器端某个定时任务触发的(比如脚本自动 SSH 出去再回来),cron 日志会显示。
结果: 14:10 只有腾讯云自身的 stargate 健康检查,没有任何服务器端任务触发 SSH 连接。这意味着连接是从客户端主动发起的。
2.8 文件系统溯源
stat ~/.ssh/authorized_keys # 查看文件创建/修改时间
cat -n ~/.bash_history | head -15 # 查看历史命令
find / -name "*ops-vm-wiki*" 2>/dev/null # 搜索磁盘上的私钥
发现:
authorized_keys创建于 2026-05-30 01:50,最后修改于 02:16- bash_history 中没有
ssh-keygen命令 → 密钥不是在服务器上生成的 - 服务器上不存在
ops-vm-wiki对应的私钥文件
2.9 用户确认与真相
经用户确认,IP 归属最终厘清:
| IP | 实际归属 | 说明 |
|---|---|---|
| 121.35.xx.xx | 公司 Kali VM | 运行另一台 Hermes Agent |
| 81.128.xx.xx | 公司物理机 | 配置了英国代理用于测试,因此出口 IP 显示为英国 |
| 120.229.xx.xx | 家里物理机 | 正常连接 |
ops-vm-wiki 密钥是 5 月 26 日在公司 Kali VM 上由 Hermes 创建的,用于该 VM push 到 GitHub wiki 仓库。公钥被同步添加到了这台服务器的 authorized_keys 中。
公司物理机 14:10 的自动连接,最可能的原因是 Obsidian Git 插件后台自动同步或 IDE(VS Code/Cursor)的 Remote SSH 保活机制。
3. 安全加固
3.1 禁用 SSH 密码登录
原理: SSH 认证有多种方式,按优先级排列:
gssapi-with-mic— GSSAPI(Kerberos)hostbased— 主机信任publickey— 非对称密钥(我们用的)keyboard-interactive— 交互式(可能回退到密码)password— 纯密码
PasswordAuthentication no 关闭第 5 种,ChallengeResponseAuthentication no 关闭第 4 种(防止 keyboard-interactive 回退到密码提示)。PubkeyAuthentication yes 显式启用第 3 种(虽然默认就是 yes,但显式声明更安全)。
操作命令:
# 修改 sshd_config(三处改动)
sudo sed -i 's/^ChallengeResponseAuthentication yes/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/^#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config
# 验证语法
sudo sshd -t
# 重载配置(不中断现有连接)
sudo systemctl reload sshd
# 确认运行时配置
sudo sshd -T | grep -E "passwordauthentication|pubkeyauthentication"
# 输出: passwordauthentication no pubkeyauthentication yes
为什么用 reload 而不是 restart: systemctl reload sshd 发送 SIGHUP 信号给 sshd 主进程,让它重新读取配置文件但不断开现有连接。systemctl restart sshd 会杀死主进程并启动新的,导致所有当前 SSH 会话断开——在远程操作时这是灾难性的(你会把自己踢下线)。
3.2 部署 fail2ban
fail2ban 是什么:
fail2ban 是一个入侵防御系统(IPS),通过监控日志文件、匹配预定义的失败模式(filter),在达到阈值后自动调用系统防火墙(iptables/nftables)封禁攻击源 IP 指定时长。
架构:
/var/log/auth.log
│
▼
fail2ban-server (Python daemon)
│
├── Filter (正则匹配日志)
│ └── sshd filter: 匹配 "Failed password for .* from <HOST>"
│
├── Action (执行封禁)
│ └── iptables: 添加 DROP 规则
│
└── Jail (Filter + Action 的组合)
└── sshd jail: filter=sshd, action=iptables-ban
工作流程:
- fail2ban-server 持续 tail
/var/log/auth.log - sshd filter 用正则匹配失败登录行,提取 IP
- 同一 IP 在
findtime(600 秒)内失败maxretry(3 次) - 触发 action:
iptables -I f2b-sshd -s <IP> -j DROP - 封禁持续
bantime(3600 秒)后自动解除
安装与配置:
# 安装
sudo apt-get install -y fail2ban
# 创建本地配置(jail.local 不会被包更新覆盖)
sudo tee /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600 # 封禁 1 小时
findtime = 600 # 10 分钟内
maxretry = 3 # 失败 3 次即封
ignoreip = 127.0.0.1/8 ::1 # 本地回环不封
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
EOF
# 启动并设置开机自启
sudo systemctl enable --now fail2ban
# 验证
sudo fail2ban-client status sshd
# 输出: Status for the jail: sshd, Currently banned: 0
配置参数解释:
findtime:滑动窗口,在此时间窗口内累计失败次数maxretry:窗口内允许的最大失败次数,超过即封禁bantime:封禁时长(秒),3600 = 1 小时- 以之前的攻击为例:51.91.64.198 在 10 分钟内尝试了上千次,第一次 3 次失败后就会被封,后续所有尝试都会被 iptables 直接丢弃(不经过 sshd),大幅减少日志噪音和 CPU 开销
验证命令:
sudo fail2ban-client status # 列出所有 jail
sudo fail2ban-client status sshd # 查看 sshd jail 状态(封禁数)
sudo fail2ban-client set sshd unbanip <IP> # 手动解封某 IP
sudo iptables -L f2b-sshd -n # 查看 fail2ban 添加的防火墙规则
3.3 为什么这些操作不影响现有登录
-
禁用密码登录:用户的三台机器(公司 Kali VM、公司物理机、家里物理机)全部使用 SSH 密钥认证(publickey),
auth.log中所有成功登录的记录都是Accepted publickey,没有任何Accepted password记录。关闭密码认证对密钥用户零影响。 -
fail2ban:只封禁失败登录的 IP。密钥认证成功的 IP 永远不会触发封禁规则。用户的 IP 在之前 4,490 次失败中从未出现过(失败的都是陌生 IP),所以 fail2ban 从未也不会封禁到用户的机器。
4. 教训与总结
| 维度 | 要点 |
|---|---|
| 告警来源 | 腾讯云告警不是一次精确的事件通知,而是对大量扫描行为的聚合告警。4,490 次失败尝试触发的 |
| 排查思路 | 先区分"成功登录"和"失败尝试",再逐层追溯:IP 归属 → 密钥指纹 → 用户确认 |
| 日志层级 | last(wtmp) 看成功 / lastb(btmp) 看失败 / auth.log 看认证过程细节 — 三层互补 |
| 安全基线 | 公网服务器必须:① 禁用密码登录 ② 部署 fail2ban ③ 仅开放必要端口 |
| 自动化隐患 | 公司物理机的自动 SSH 连接(可能是 Obsidian Git 插件/IDE 后台)触发了最初的可疑 IP 告警——即使实质无害,也需要排查以确认来源 |
5. 相关页面
- 从零购买并配置云服务器 — 服务器初始部署记录
- Hermes Agent 安装与踩坑实录 — Hermes Agent 安装与配置
- 微信 Gateway 接入 Hermes — 微信 Gateway 接入