凌晨两点,手机连着响了三声。我摸过手机一看,是服务器监控推送:CPU使用率飙到 98%,外网出方向流量在五分钟内跑了 2GB 多。这台机器是我手里一台挺重要的业务服务器,平时负载一直很平稳。当时脑子“嗡”了一下,第一反应是:被入侵了。
不做任何多余动作,我先截图保存告警数据,然后立刻爬起来打开电脑。这篇文章就是我当时处理这一整件事的完整记录。从发现异常、快照备份、断网隔离,到日志溯源、揪出恶意进程、清理后门,再到后续的系统加固,整个过程大概持续了四个多小时。如果你手里也管着服务器,或者你正准备把服务部署到公网,这篇文章应该能帮你省掉不少半夜爬起来踩坑的代价。
1. 凌晨两点,监控探针响了
1.1 异常现象与第一时间判断
先说这台服务器的大致情况:一台 4 核 8G 的云服务器,系统是 Ubuntu 20.04,主要跑着一个 Nginx 服务和一个 Python 写的业务 API。平时 CPU 占用率基本在 5% 到 15% 之间波动,流量也很规律,白天高一点,凌晨很安静。
可那天凌晨,监控面板上出现了几个非常刺眼的信号:
- CPU 使用率瞬间冲到 98% 以上,load average 直接到了 6 左右;
- 出方向流量暴增,几分钟内上传了超过 2GB 数据;
- 登录审计日志里出现了大量来自陌生 IP 的 SSH 失败尝试记录;
- 某个不认识的进程名占用了接近 400% 的 CPU,明显是多线程在跑。
单看其中一条可能还有解释空间。比如流量暴增也许是业务方在大量同步数据,但 CPU 持续 98% 加上陌生进程、失败登录记录,这几个信号同时出现,基本可以断定机器已经被入侵了。
1.2 为什么先别慌着杀毒
很多人第一反应是“赶紧杀掉那个可疑进程”或者“把文件删了”。但作为运维,我的建议是:在完成证据固定之前,不要急着杀毒、改密码、重启服务。
原因很简单。入侵者往往不会只留一个后门。你杀掉一个表面上的挖矿进程,他可能已经写好了定时任务、替换了系统命令、设置了自启动服务,甚至留下了几组 SSH 公钥。你手动清理掉一处,过两个小时他又回来了,而且你还没有任何线索可以追溯他是怎么进来的。
所以真正的第一步永远是:固定现场。把内存信息、进程列表、网络连接、登录日志、可疑文件都留存下来,然后再做隔离和清理。这就像出了交通事故先拍照留证再挪车,道理一样。
2. 应急响应:先保数据,再谈查杀
2.1 断网 + 快照——最稳妥的第一步
我当时的操作顺序是:
- 立刻在云控制台给这台服务器创建磁盘快照。这一步是保险中的保险,万一后续误删了重要文件,或者清理过程中把系统搞坏了,可以随时回滚。
- 在云平台安全组里先把所有外网入方向规则临时删掉,仅保留我当前电脑的 IP 白名单访问 SSH。这就相当于把门先锁上,只让自己人进出。
- 登录服务器后,先执行
history查看当前 shell 历史记录,同时把内存里正在跑的进程列表 dump 下来。
这里特别说一下快照。快照不是备份,但它是事故现场的“原始照片”。如果你不打算给入侵者留任何退路,快照也是后续分析的素材库。我当时给数据盘和系统盘都做了快照,后面排查时多次在这个快照上比对系统文件,帮助非常大。
另外要注意,断网不是你手动拔网线,而是通过安全组或防火墙规则把入方向流量先切断。云服务器通常有安全组,直接在控制台调整最安全,也不会影响到 VNC 之类的管理通道。如果你在物理机上,可以先把网卡 down 掉,但前提是你确定自己能再通过本地控制台进入系统。
2.2 保持现场:哪些操作千万别做
在拿到关键证据之前,有几件事我强烈建议不要做:
- 不要直接杀掉可疑进程。很多恶意进程会配合守护进程,你杀掉主进程,守护进程几秒内又给你拉起来,反而暴露了你的操作。
- 不要直接删除恶意文件。文件里可能藏着入侵者的 IP、C2 地址、攻击组件版本,这些信息丢了你只能靠猜。
- 不要重启服务器。有些 Rootkit 或内存马只在内存里存活,一重启就没了,看起来“病好了”,但你也失去了最直接的证据。而且如果对方写了开机启动项,重启后反而会让他更容易控制机器。
- 不要直接在原系统里改密码。原因不是不能改,而是先确认对方手里是否已经有你系统的控制权。如果你在被他种了键盘记录器的环境里改新密码,等于把新密码也送出去了。
我当时的做法是:先在自己的工作电脑上准备一个干净的排查箱,用 SSH 密钥方式连接,然后把所有现场数据导出到本地。现场没动,只读操作,接下来的排查才不会被干扰。
3. 溯源排查:我到底是怎么被进来的
3.1 日志里的爆破痕迹与第一线索
排查的第一步永远是看日志。Linux 系统里最重要的几份日志文件是:
/var/log/auth.log或/var/log/secure:SSH 登录认证日志,记录成功和失败的登录尝试;/var/log/wtmp和/var/log/btmp:记录成功登录和失败登录的历史;/var/log/syslog或/var/log/messages:系统整体运行日志;last和lastb命令:快速查看最近登录记录。
我当时先跑了一条命令:
last -20结果一眼就看到一个来自陌生 IP 的账号ubuntu在凌晨 1 点 47 分左右有过一次登录记录。那个时间点我确定自己没登录过。再配合lastb查看失败登录记录,前面还有几百条来自同一个 IP 段的 SSH 爆破记录。
这说明什么?说明对方很有可能是通过 SSH 爆破进来的。那问题来了:我的服务器密码真的这么弱吗?
我赶紧检查了一下自己的 SSH 配置,当场差点没被自己气死。这台机器是两个月前一个同事临时交付测试用的,sshd_config里居然开着PermitRootLogin yes,而且系统里还留着一个弱密码账号。密码强度虽然不算弱到 123456,但架不住对方字典大,连续爆破了几百次,最后还是进来了。
这里提一句,看日志的时候一定要结合时间线。不要只看失败记录,要找“失败之后紧接着成功”的那一条,那是攻击者真正进来的“开门瞬间”。我在 auth.log 里搜到了这样一段:
grep "Accepted" /var/log/auth.log | tail -20输出里果然有一行 Accept 记录,紧接着就出现了一个非常可疑的命令执行行为。这意味着攻击者在成功登录后立刻执行了后续的下载和运行动作。
3.2 进程、端口与文件的全面扫描
拿到“已经进来过”这个结论之后,我开始盘查当前系统里到底有什么异常。
先看进程,用ps按 CPU 和内存排序:
ps aux --sort=-%cpu | head -30结果第一行就出现了一个名字很可疑的进程,运行在/tmp/.X11-unix/目录下,CPU 占满了所有核心。这个路径本身就是异常标识,正常程序根本不会把可执行文件放在 /tmp 下的隐藏目录里。执行ls -la /tmp一看,果然有几个修改时间在凌晨 1 点 50 分左右的文件,命名还都伪装成系统文件,比如kworker、sysupdate之类的。
再看网络连接,确认它到底在往哪里传数据:
ss -tnp这一看吓了一跳,进程已经和十几个外部 IP 建立了 TCP 连接,其中有几个端口是常见的 IRC 端口和 4444 端口。虽然不是所有连接都能立刻定性为恶意,但一个挖矿木马和外部 C2(控制端)建立长时间连接是非常典型的行为。
接着我检查了有没有替换系统命令。攻击者常常会把ps、netstat、top等命令换成 Rootkit 版本,让你看到的信息是“被美化”的假象。对比方式很简单,可以用系统包管理器校验文件的完整性,比如 Debian/Ubuntu 上可以用dpkg -V:
dpkg -V | grep -E 'bin/(ps|netstat|top|ls|ss)'如果输出中出现5M这类标记,就说明文件确实被改动过。好在我的系统里命令没被替换,否则后续清理难度会直线上升。
3.3 定时任务、启动项与 SSH 公钥里的后门
进程排查只能看到当前在跑的东西,真正麻烦的是“下次开机还会回来”的持久化后门。这一步我检查了四个地方:
- 当前用户的 crontab:
crontab -l - 系统级的定时任务:
cat /etc/crontab、ls /etc/cron.d/ - systemd 服务:
systemctl list-unit-files | grep enabled - SSH 授权公钥:
cat ~/.ssh/authorized_keys
每个地方都发现了异常。
当前用户的 crontab 里多了一条任务,内容大致是“每两分钟从指定地址下载一个脚本并执行”。由于脚本已经下载到本地,我没有直接删掉,而是先看了一下脚本内容,确认这是一个典型的恶意下载器:它不直接挖矿,而是先轮询 C2 地址,随时准备接收新的指令模块,同时还会把自己复制到/var/tmp/下做备份。
SSH 公钥那边更离谱,~/.ssh/authorized_keys里多了三把公钥,其中一把的注释信息明显是攻击者的 ID。这说明他不仅拿到了账号权限,还把自己“钥匙”留在了门上。哪怕你改了密码,只要这公钥还在,他随时还能用密钥直接进来。这也是为什么我一直强调,判断一台服务器是否被入侵,不能只看进程和流量,要全面看持久化入口。
系统服务那边也被加了一个systemd服务文件,指向一个二进制文件,服务描述写成了“system update daemon”,但其实就是一个守护进程。这意味着就算我手动杀了挖矿主进程,systemd 也会在几秒后把它重新拉起来。好在 systemd 服务文件是明文,直接可以确认它的二进制路径。
4. 清理与加固:从杀毒到封闭出入口
4.1 清除后门文件与恶意进程
所有证据固定完之后,才开始进入清理阶段。我的清理顺序是这样:
- 先删除恶意 systemd 服务,并执行
systemctl daemon-reload,防止系统再把进程拉起来。 - 删除 crontab 里的恶意任务和下载脚本,同时把
/var/spool/cron/crontabs/下的异常文件也清掉。 - 删除 SSH 授权公钥里的陌生公钥。
- 再杀掉恶意进程的进程树,使用
kill -9之前先找到它的所有子进程,确认没有残留。 - 最后检查
/tmp、/var/tmp、/dev/shm下的隐藏文件,一并清理。
这里特别提一个容易踩的坑:很多恶意程序会在进程结束前把自身文件删掉,或者持续写入子进程。所以顺序一定不能反。先堵住“自动复活”的入口,再杀进程。如果上来就kill -9,systemd 很快又给你拉一个新的起来,反而会让你反复折腾。
清理时我也顺手查了下攻击者在这台机器上执行过的历史命令,~/.bash_history里能看到一些下载记录。这里多说一句,如果你发现.bash_history是空的或者只有很少的记录,不代表他没操作,很多攻击者进来第一件事就是unset HISTORY清掉 shell 历史。所以历史文件只能作为辅助线索,不能当唯一依据。
4.2 SSH、防火墙、fail2ban 组合加固
清完现场,接下来才是治本:把入口彻底封死。
第一件事,关掉 root 远程登录,改成密钥登录。修改/etc/ssh/sshd_config:
PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes改完执行systemctl restart sshd。这一步的意义是:即使对方还知道某个账号的密码,也没办法用密码直接登录了。密钥认证的强度远高于密码,尤其能挡住大多数纯字典爆破。
但只改 SSH 配置还不够,还要配合防火墙把无效端口全部挡在外面。我用的ufw,规则很简单:
ufw default deny incoming ufw allow 22/tcp # 实际我后来把 SSH 改到了非标准端口 ufw allow 80,443/tcp ufw enable如果你用的是云服务器,还要记得安全组规则同步收紧,只放行必要的端口和来源 IP。经常有人只在系统防火墙里改了,但云安全组还是全放通,等于白搭。
第二件事,部署 fail2ban。这个工具基本是服务器标配了。它的思路很简单:如果在规定时间内某个 IP 连续登录失败超过阈值,就自动把这个 IP 拉黑一段时间。我用的配置如下:
[DEFAULT] bantime = 3600 findtime = 600 maxretry = 5 [sshd] enabled = true安装和启动也很直接:
apt install fail2ban systemctl enable fail2ban systemctl start fail2ban部署完之后的几个小时里,我看了一眼 fail2ban 的日志,几十个爆破 IP 全部被自动封了。凌晨那种“几百条失败登录”的场景,以后再出现也会被挡在门外,折腾不了几个回合。
4.3 系统更新与最小化服务
后门清了,SSH 也堵上了,接下来要做的是给整个系统“补基础”。很多攻击者能进来,靠的不一定是弱密码,有可能是系统里某些服务存在已知漏洞。我的服务器上之前装了一堆测试组件,其中就包括一个旧版本的 Redis,绑定在公网地址上还没设密码。虽然这次入侵不一定是通过 Redis 进来的,但这种“公网裸奔”的服务,本身就是个巨大的风险点。
我当时的处理办法是:
- 先执行系统更新,把内核和关键软件包都升到最新版本:
apt update && apt upgrade -y - 关闭不必要的服务,尤其是数据库、缓存、消息队列这类组件,全部改成仅监听内网地址或 Unix socket。
- 检查所有用户账号,删除掉已经不用的测试账号,并要求所有留存账号改成高强度密码。
这里也建议大家顺便看一下系统时间同步。排查时如果系统时间和真实时间差太多,日志时间线都会对不上,很容易漏判。我当时顺手装了 chrony:
apt install chrony systemctl enable chrony让服务器自动和标准时间服务器保持同步。这虽然和入侵没有直接关系,但后续做日志审计时,统一的时间基准能省掉很多麻烦。
5. 复盘与日常防线:这次踩的坑,下次不用再踩
5.1 我的攻击面盲区
处理完已经是凌晨五点多,坐在电脑前复盘的时候,我发现这次能进来,根本原因全在平时偷的懒:
- 这台服务器虽然是正式业务在用,但交付过程很随意,SSH 居然开着 root 密码登录,这是最大的口子;
- 弱密码账号没有及时清理,相当于给攻击者留了一扇侧门;
- 系统组件版本偏旧,且有些调试服务直接暴露在公网,增加了被利用面;
- 第三方的监控只覆盖了 CPU、内存、流量这些基础指标,但对登录日志、文件完整性、新建系统服务这些“入侵指标”完全没关注。
这些问题单独看都不起眼,但叠加在一起,就变成了一次完全可以避免的入侵。对绝大多数中小企业或个人开发者来说,被入侵从来不是某个“高深漏洞”,而是各种低水平配置失误的叠加。
5.2 建议长期开启的监控项
这次之后,我给服务器加了一圈防护和监控。如果你也管着服务器,可以参考一下:
| 监控项 | 方式 | 作用 |
|---|---|---|
| 登录失败次数 | fail2ban + 云监控 | 防止 SSH 暴力破解 |
| 账号新增与公钥变更 | 定期脚本比对 authorized_keys | 发现异常后门入口 |
| 系统服务变更 | systemctl list-unit-files定期快照 | 发现异常开机启动项 |
| 文件完整性 | auditd或tripwire | 发现关键文件被篡改 |
| 出方向流量 | 云监控或 vnStat | 发现挖矿木马上传数据 |
| 对外连接数 | ss -tnp定时采集 | 发现异常反弹连接 |
另外,我强烈建议开启云厂商自带的安全告警,比如“异常登录提醒”和“疑似挖矿行为检测”。这些情报库比个人经验全得多,很多你还没反应过来的异常,它会先给你发一条消息。虽然告警有时候会误报,但“误报”永远比“漏报”好。
我现在的习惯是每周抽十分钟看一遍/var/log/auth.log里的 Accepted 记录,再手动比对一下系统里有没有新增用户、新增 SSH 公钥、新增 systemd 服务。长期坚持下来,最大的好处不是你能防住所有攻击,而是你对这台服务器的“正常状态”越来越敏感。一旦出现一点点异常,你立刻就能感知到,不用像我这次一样等到凌晨被监控喊醒。
最后再分享一个小技巧:如果你发现服务器已经被入侵了,别急着清理,先把ps aux、ss -tnp、last -20、crontab -l这几条命令的输出都保存到本地。这些快照就是你后续分析入侵路径的全部依据。很多人一到半夜人一慌,上来就把进程杀掉、文件删了,等到想查他是怎么进来的时候,现场已经什么都没了。保持冷静,按步骤来,服务器被入侵这件事,虽然有惊,但真的不用慌。