内网服务器为何频频出现来源不明的免密 SSH 登录?一次 authorized_keys 后门排查实录

运维跳板机频繁出现陌生公网IP的免密SSH登录,溯源发现 authorized_keys 被植入攻击者公钥形成持久化后门

问题背景

公司运维跳板机(堡垒机前的咽喉节点)是进入整个内网的单一入口,平时只允许运维同事从公司固定出口 IP 或 VPN 段以密钥方式登录,root 直接登录被禁用、口令认证被关闭。某次例行安全巡检,我在把 /var/log/secure 往 SIEM 归集时,注意到一批来源为境外云厂商段的 “Accepted publickey” 记录——但这些 IP 根本不在公司出口白名单里,团队也没人从那些地址登过机。跳板机一旦失陷,攻击者就能以此为支点横向移动打穿整个内网,因此这条异常必须立刻查清。

故障现象

异常登录线索非常"安静",没有任何资源告警,但日志细节很反常:

  • /var/log/secure 里大量出现 Accepted publickey for root 记录,来源 IP 多变,集中在几个境外 VPS 网段,而公司出口是固定 IP,这些地址明显不在白名单。
  • 直觉以为是 SSH 口令爆破,但日志里完全没有 Failed password,全是直接 Accepted——说明对方用的是合法公钥,而非猜密码。
  • 这些会话大多极短:登录后执行 whoamicat /etc/shadowuname -a,再 curl 下载一个小脚本后退出,没有长时间驻留,也没有挖矿进程,CPU、流量曲线平稳,传统监控毫无反应。
  • last / wtmp 显示对应会话确实存在,但 lastlog 里这些来源此前从未出现。
  • fail2ban 没有拦截,因为它默认只针对密码失败计数,公钥免密登录根本不触发其规则。

最关键的矛盾点:登录成功了,但持有凭据的"人"不是我们。这已经不是探测,而是后门已经落地。

排查过程

第一直觉要纠正:看到 SSH 异常,很多人先查爆破,但 Failed password 一条都没有,说明问题不在"猜密码",而在"已有合法钥匙"。于是排查方向立刻转向密钥与信任链。

1. 确认登录方式与账号。 grep "Accepted" /var/log/secure | tail -n 50 显示全部是 publickey,账号清一色 root 或高权运维账号,来源 IP 集中在几个境外段。结合背景(已禁用 root 直接登录、关闭口令),能公钥登 root 的,只能是写进 authorized_keys 的钥匙。

2. 直奔密钥文件。 检查 /root/.ssh/authorized_keys,发现文件末尾多出两长串陌生公钥,注释名还故意伪装成 gitlab-runnerjenkins-deploy 这类可信身份,混在团队正常密钥之间,不仔细 diff 根本看不出来。用 ssh-keygen -l -f authorized_keys 列出指纹,陌生的那两把正是境外会话用到的公钥。

3. 还原写入时机。 stat /root/.ssh/authorized_keys 看到 mtime 落在某次"看似正常"的发布窗口。翻 historybash 审计日志,定位到当时有一行被夹在正常命令里的 echo "ssh-rsa AAAA... attacker" >> ~/.ssh/authorized_keys——执行者要么是已被控的运维终端,要么是利用泄漏凭据自动化写入的脚本。

4. 追溯初始入侵链。 往后翻 /var/log/secure 与开发机日志,发现数日前有一次来自内网某开发机的登录记录,该开发机此前踩过内部 pip 源投毒(恶意依赖包),攻击者在开发机上拿到了跳板机的长期私钥后登录,再"公钥落地"完成持久化。也就是说,后门只是第二步,第一步是开发机凭据泄漏

5. 排查其他持久化与篡改。 不能只看 authorized_keys:依次检查 crontab -l/etc/cron.*systemd timer、/etc/rc.local/etc/profile.d//etc/ld.so.preload(LD_PRELOAD 劫持)、/etc/pam.d/(是否被追加 pam_permit 或恶意模块)、以及 /etc/ssh/sshd_configPermitRootLoginPasswordAuthentication 是否被偷偷改回 yes)。本例仅发现 authorized_keys 被改,其余干净,但仍需全量轮转凭据。

6. 评估横向影响。 在 secure 里检索从该跳板机主动发起的、到其他内网主机的 ssh 记录,并检查 ~/.ssh/known_hosts~/.ssh/config 里记录的跳板目标与已可达密钥,确认攻击者是否借跳板机进一步移动。本例横向动作有限,但为保险仍按"已失陷"处置。

解决方案

立即止血(清除):/root/.ssh/authorized_keys 中剔除所有陌生公钥,只保留配置管理系统(Ansible)下发的受控密钥;清理后 chmod 600chattr +i 设为不可变,防止再次被追加。同时吊销并轮转所有从该跳板机可达的密钥与口令,强制离线重置 root 口令。

重建信任(而非杀进程): 由于后门是"持久化+信任链污染",简单 kill 会话没用——对方随时能再用那把公钥登回来。正确做法是将跳板机从网络隔离,用干净镜像或可信备份重建系统,重建后才重新接入。保留原始磁盘镜像与日志用于取证与上报。

阻断入口: 在堡垒机/防火墙层把 SSH 来源收敛为公司出口 IP + VPN 段,公网一律拒绝直达;给 fail2ban 增加规则——对短时间内出现多个陌生来源 Accepted publickey 的情况做临时封禁与告警。

取证留存: 全程保留镜像、内存与日志副本,记录 IOC(可疑 IP、公钥指纹、下载脚本哈希),同步给安全团队做归因与威胁情报沉淀。

根因分析

根因是"信任链被植入"。攻击者先通过开发机的恶意依赖/凭据泄漏取得跳板机初始访问权,再向 authorized_keys 写入自有公钥,把"需要口令或受控私钥"的门槛,降为"持有另一把私钥即可",形成免密持久化后门。它绕过了密码爆破检测,也比 XMRig 类挖矿更隐蔽——不占资源、不触发传统告警,只为长期保留一道安静的门。真正的破绽不是"登录成功",而是"成功来源不该成功"。

预防措施

  • 收敛信任: 禁止 root 直接 SSH,关闭 PasswordAuthentication,仅允许受控密钥;authorized_keys 只能由 Ansible 等配置管理写入,写完后 chattr +i 设为不可变,并监控其哈希变化。
  • 来源白名单: 跳板机 SSH 只允许公司出口 IP 与 VPN,公网拒绝;统一收口到堡垒机,杜绝主机直连。
  • 入侵检测: 部署 auditd 监控 ~/.ssh 目录写操作与 sshd_config 变更;HIDS 对"新公钥落地"告警;日志集中到 SIEM,对"Accepted publickey 且来源非白名单"做实时规则。
  • 最小权限与隔离: 跳板机不长期存放业务密钥,改用 Vault 等即时凭证;开发机与跳板机网络隔离,避免凭据跨域污染。
  • 供应链与凭据治理: 内部 pip/npm 源做包完整性校验(哈希/签名),CI 凭据最小权限并定期轮换,从第一步掐断初始入侵链。

总结

这次事故最大的教训是:免密登录成功 ≠ 安全,反而可能是后门已落地的信号。和显眼的挖矿木马不同,authorized_keys 后门不消耗资源、不触发传统告警,只是安静地替攻击者保留一道门。排查的关键,是从"Accepted publickey 但来源可疑"这一反常切入,顺藤摸到被篡改的密钥文件与背后的初始入侵链。防御上,把信任收口到堡垒机 + 来源白名单 + 不可变密钥文件,远比事后清剿重要——门装好了,后门就无从落地。

使用 Hugo 构建
主题 StackJimmy 设计