我被黑了

10

唉,之前我一直觉得:

个人小破站嘛,又不是大公司生产环境,应该没人盯我吧。

结果现实狠狠给了我一巴掌。

这次不是演练,不是靶场,也不是 CTF。

是真的被黑了。CPU 飙高,机器里被塞了挖矿程序、后门服务、隐藏动态库,甚至还有把机器当代理节点卖带宽的东西。

第一次遇到这种事,老实说还是挺吓人的。

事情的起点

我的 Home Lab 大概是这样:

  • 家里一台 PVE。
  • 上面跑了一堆 VM。
  • 里面还有一个 K8s 集群。
  • 公网入口放在云服务器上。
  • 云服务器通过 FRP 把流量转回家里。
  • 平时跑博客、导航页、镜像站、Alist、监控、RAG,还有一些 K8s 实验。

架构看起来还挺像模像样:

用户浏览器
-> 公网 VPS
-> FRP
-> 家里 proxy VM
-> K8s Ingress
-> 各种服务

当初搭起来的时候还挺开心,感觉自己终于拥有了一个小型“数据中心”。

然后我就犯了一个非常经典的错:

为了方便,我把 SSH 也通过 FRP 映射到了公网。

而且还是密码登录。

而且 root 也能登。

现在回头看,这已经不是“留了个门”,这是把门牌号、门铃和备用钥匙一起挂门口了。

最早发现不对劲

最开始发现问题,是因为 master 机器 CPU 不正常。

一开始我还以为是 K8s 抽风。

毕竟 master 上跑着控制面组件,什么 kube-apiserver、etcd、controller-manager、scheduler,看起来谁都有嫌疑。

我甚至还怀疑过是不是某个 Pod 起飞了,或者监控组件太能吃。

结果越查越不对劲。

机器里出现了一些看着就很不妙的东西:

/etc/ld.so.preload
/usr/local/lib/sshdd.so
/etc/systemd/system/log.service
/usr/bin/log
/var/log/log
/var/tmp/cli
/var/tmp/traff
/etc/log

看到 /etc/ld.so.preload 的时候,我心里基本凉了一半。

这玩意不是普通脚本误跑。

它可以让系统在启动程序时先加载某个动态库,常见玩法就是隐藏进程、隐藏文件、劫持命令输出。

也就是说,机器不只是“跑了个坏程序”,而是已经被人动到系统层了。

后面继续查,还看到:

  • 有伪装成 systemd 的可疑用户。
  • sudoers 里出现过免密配置。
  • 登录日志被清过。
  • 一些系统命令和包状态被动过。

这时候就不用嘴硬了。

不是故障,是入侵。

入侵路线

最后把日志、FRP 配置和时间线串起来,路线大概是这样:

公网扫描
-> VPS 上的 frps
-> 家里 proxy VM 上的 frpc
-> master SSH 映射
-> root 密码爆破成功
-> 写后门、挖矿、清日志

关键点在 SSH 日志。

在出事时间附近,master 上能看到大量 root 登录失败。

来源看起来是家里的 proxy VM。

但这不代表 proxy VM 是攻击者本体。

因为 FRP 转发过来之后,master 看到的来源就是内网 proxy。

真正的公网来源,要去 VPS 的 frps 日志里找。

而 frps 日志里,也确实能看到有人频繁连接那个 master SSH 映射。

所以这次不是玄学,也不是 K8s 神秘漏洞。

真正的入口非常朴素:

公网暴露 SSH + root 密码登录 + 密码被猜中

很土。

但很有效。

一开始我还怀疑 K8s

因为被打的是 master,所以第一反应真的很容易往 K8s 上想。

是不是 apiserver 暴露了?

是不是某个服务有漏洞?

是不是 Ingress 被扫了?

是不是哪个镜像不干净?

但证据不支持。

真正能连成线的是:

  • FRP 里确实存在 master SSH 映射。
  • master 当时允许 root 密码登录。
  • 日志里有大量 root 爆破。
  • 某个时间点出现了一次密码登录成功。
  • 后门文件落地时间和这个时间点能对上。

所以这次的结论很简单:

K8s 背锅了,但它不是入口。

锅在我自己把 SSH 放到公网,还让密码登录。

那些后门都在干嘛

我把可疑文件先挪走保存,没有运行样本,只做静态分析。

里面大概有几类东西。

1. 挖矿程序

这个应该就是 CPU 高的主要原因。

样本里能看到类似这些关键词:

XMRig
miner
pool
stratum

基本就是门罗币挖矿那套。

表现也很符合:CPU 长时间很高,机器风扇和负载都不太对劲。

2. 隐藏组件

/etc/ld.so.preload 配合可疑 .so 动态库,常见用途就是隐藏恶意进程和文件。

这也是最让我不舒服的地方。

因为它会影响你“看到的系统状态”。

也就是说,机器可能已经不完全可信了。

你执行 lspstop,看到的东西不一定是真的。

这感觉就像在自己家里找东西,但灯是别人控制的。

3. 持久化服务

还有一个伪装得很随意的 systemd 服务:

log.service
ExecStart=/usr/bin/log -sshd
Restart=always

名字叫 log,看起来像系统日志。

但实际是在拉恶意程序。

Restart=always 的意思也很直白:

你杀了它,它还会起来。

4. 清理痕迹

样本脚本里还能看到清日志的行为。

比如删 auth.logwtmpbtmplastlog.bash_history 之类的东西。

这也解释了为什么后面 last 查登录历史不完整。

攻击者不是进来随便看看。

它是很熟练地落地、隐藏、持久化、清痕迹。

5. 带宽变现代理

除了挖矿,还有一个东西不怎么吃 CPU,但也很恶心。

它会把你的机器当代理节点,拿去卖带宽。

样本里能看到类似这些字符串:

TraffMonetizer
Inbound
Outgoing
EarnLast30Days

所以这台机器不只是被拿去挖矿。

还可能变成别人访问互联网的出口。

这件事比 CPU 高更麻烦,因为出口 IP 是你的。

处理方案

当时处理顺序大概是这样:

  1. 先停 proxy 上的 frpc。
  2. 禁止 frpc 自动重启。
  3. 关掉 master SSH 的公网映射。
  4. 在 PVE 上临时限制 proxy 的转发。
  5. 把 master 上的可疑文件挪到证据目录。
  6. 删除后门用户和 sudoers 免密。
  7. 恢复被动过的系统命令和包状态。
  8. SSH 禁止密码登录。
  9. 整理样本、哈希、时间线。

最重要的是前面三步:

先断入口,再清后门。

如果入口还开着,清理就是跟对方拔河。

你这边删一个,它那边再种一个。

真的会把人整麻。

最后恢复业务

确认入口关掉、后门清掉之后,我才慢慢恢复业务。

恢复的时候也没有一口气全开。

先恢复博客、导航页、镜像站这些 HTTP 业务。

管理口就先别急着往公网放了。

这次最应该记住的就是:

业务入口和管理入口一定要分开。

公网可以有博客。

公网可以有普通 Web 服务。

但 SSH、ArgoCD、Harbor、Prometheus 这种管理面,最好不要裸奔在公网。

真要远程管理,老老实实用 VPN、Tailscale、WireGuard,或者至少做 IP 白名单。

这次最大的教训

个人服务也会被扫

我以前总觉得:

我一个个人博客,谁闲着没事搞我?

后来发现,攻击者根本不需要认识我。

它也不关心我是谁。

它就是扫公网。

扫到 SSH,开始爆破。

撞上一个密码,自动投递脚本。

一套流水线走完,机器就开始替别人赚钱。

这不是“有人盯上我”。

这是互联网的背景噪音。

不要把 SSH 映射到公网

尤其不要这样:

公网端口 -> FRP -> 内网 SSH

这个链路太舒服了。

舒服到攻击者也很喜欢。

以后我的原则是:

  • 公网只放必要的 HTTP/HTTPS。
  • SSH 不走公网 FRP。
  • 远程管理走 VPN。
  • 管理入口加白名单。
  • 密码登录关掉。
  • root 登录关掉或限制为密钥。

密码登录真的该关

SSH 最少也要这样:

PasswordAuthentication no
PermitRootLogin prohibit-password

更严格一点就是:

PermitRootLogin no

密码登录只要放公网,就迟早被撞。

不是今天,就是以后。

被 rootkit 碰过的机器不要太信

这次出现了 ld.so.preload、可疑动态库、systemd 后门、清日志行为。

所以即使清理完,也不应该长期完全信任这台机器。

最稳的方案还是:

备份业务数据
重装系统
重建节点
重新加入集群
轮换所有密码和 token

清理适合止血。

重建才是安心。

小结

这次真的算是给我上了一课。

之前搭内网穿透的时候,我还觉得自己挺聪明:

家里服务终于能从公网访问了,爽!

结果这次发现,方便是真的方便,危险也是真的危险。

Home Lab 可以是玩具,但公网不是游乐场。

只要你把端口放出去,它面对的就是全世界的扫描器、爆破器和自动化脚本。

这次还好,没什么重要业务,主要损失是时间、心态和一点电费。

但它把一个事情讲得非常明白:

不要因为是个人项目,就把安全当成以后再说。

以后继续折腾。

但该关的门,还是得关。

不然下一次 CPU 又起飞,我可能就没这么淡定了。