总结 study · 研习 #server, devops, tooling 2026-06-02

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 机制: 每当用户登录/登出,loginsshd 会调用 pam_lastlog 将登录会话信息写入 /var/run/utmp(当前在线)和 /var/log/wtmp(历史记录)。这是一个二进制文件,普通文本工具读不了,必须用 last 解析。

发现:

2.2 失败登录记录:lastb

sudo lastb -20

为什么用这个命令: lastblast 的对称命令,读取 /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
...

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

然后过滤确认没有被密码成功登录的:

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  # 搜索磁盘上的私钥

发现:

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 认证有多种方式,按优先级排列:

  1. gssapi-with-mic — GSSAPI(Kerberos)
  2. hostbased — 主机信任
  3. publickey — 非对称密钥(我们用的)
  4. keyboard-interactive — 交互式(可能回退到密码)
  5. 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

工作流程:

  1. fail2ban-server 持续 tail /var/log/auth.log
  2. sshd filter 用正则匹配失败登录行,提取 IP
  3. 同一 IP 在 findtime(600 秒)内失败 maxretry(3 次)
  4. 触发 action:iptables -I f2b-sshd -s <IP> -j DROP
  5. 封禁持续 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

配置参数解释:

验证命令:

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 为什么这些操作不影响现有登录

  1. 禁用密码登录:用户的三台机器(公司 Kali VM、公司物理机、家里物理机)全部使用 SSH 密钥认证(publickey),auth.log 中所有成功登录的记录都是 Accepted publickey,没有任何 Accepted password 记录。关闭密码认证对密钥用户零影响。

  2. 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. 相关页面