监控告警显示 CPU 长期占用 90% 以上,但你看了一圈业务进程都很正常;带宽出方向莫名跑满;SSH 日志里出现了不认识的成功登录;或者更隐晦一些——什么告警都没有,只是电费和云账单涨了。这些都可能是服务器被植入了恶意进程。遇到这种情况,最忌讳两件事:一是立刻 kill -9 然后以为万事大吉(进程会自己复活),二是直接重装系统(证据全丢,入口没找到,重装后很快再次沦陷)。本文给出一套完整的处置流程:先识别、再取证、再定位入口、最后清理加固。
一、异常信号:怎么察觉机器不对劲
恶意进程通常会在以下几个维度留下痕迹,任何一个都值得深挖:
- 资源异常:CPU 长期高占用但业务量没涨、出方向带宽持续跑满、内存被莫名吃掉。
- 连接异常:存在到陌生 IP 的长连接,尤其是连接到境外地址或非常规高位端口。
- 持久化痕迹:新增的定时任务、新增的 systemd 服务、
authorized_keys里多出陌生公钥。 - 账号异常:新增的 UID 0 账号、长期不活跃账号突然有登录记录。
- 文件异常:
/usr/bin、/tmp、/dev/shm下出现可疑可执行文件,或系统命令被替换(ps、top、netstat输出"太干净"本身就是信号)。 - 外部反馈:机房通知对外攻击、IP 被列入垃圾邮件黑名单、用户投诉网站被跳转。
二、识别:找到那个不对劲的进程
2.1 先看进程与资源占用
# 按 CPU 排序查看进程(PPID 与 ETIME 很关键) ps -eo pid,ppid,user,%cpu,%mem,etime,cmd --sort=-%cpu | head -30 # 以进程树形式查看,能看到父子关系(父进程为 1 或异常 PID 时要警惕) ps auxf # 静态输出一次 top top -b -n 1 | head -40 # 重点关注 /tmp、/dev/shm、/var/tmp 下启动的进程 ps -eo pid,user,cmd | grep -E '/tmp|/dev/shm|/var/tmp' | grep -v grep
看进程时要特别留意这几个特征:进程名伪装成系统进程(如 kworker/0:1、systemd-daemon 但路径在 /tmp);可执行文件路径已被删除(/proc/PID/exe 显示 (deleted));启动时间与某次异常登录时间吻合;父进程是 Web 服务(说明可能是通过 Web 漏洞进来的)。
2.2 深挖单个进程的详情
# 查看进程对应的真实可执行文件(deleted 表示文件已被删除但进程仍在跑) ls -l /proc/1234/exe # 查看完整命令行(cmdline 以 NUL 分隔,用 tr 转换后可读) tr '\0' ' ' < /proc/1234/cmdline; echo # 查看进程工作目录与环境变量 ls -l /proc/1234/cwd tr '\0' '\n' < /proc/1234/environ | head -20 # 查看进程打开的文件与网络连接 lsof -p 1234 ls -l /proc/1234/fd | head -30
2.3 看外连:恶意进程往往要"回家"
# 查看所有连接及对应进程
ss -antp
# 只看已建立连接
ss -antp | grep ESTAB
# 统计本机连接到哪些远端地址
ss -antp | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20
# 用 lsof 交叉核对(当系统命令可能被替换时,两个工具结果不一致就是信号)
lsof -i -nP
# 查看监听端口是否有异常新增
ss -lntup
2.4 检查隐藏进程与 rootkit
如果 ps 看不到但资源确实被消耗,可能是命令被替换或有内核态隐藏。这时要用独立的检测工具交叉验证:
# 安装与使用(以 Debian/Ubuntu 为例) apt install -y rkhunter chkrootkit unhide # rkhunter:先更新特征库再检查 rkhunter --update rkhunter --check --sk # 结果在 /var/log/rkhunter.log,重点看 Warning 项 # chkrootkit:检查常见 rootkit 与命令替换 chkrootkit # unhide:检测被隐藏的进程与端口 unhide proc unhide sys unhide-tcp # 校验系统命令是否被篡改(RHEL 系用 rpm -Va,Debian 系用 dpkg --verify) rpm -Va 2>/dev/null | head -30 dpkg --verify 2>/dev/null | head -30
一个实用技巧:准备一份静态编译的 busybox 放在机器上,用 busybox ps、busybox netstat 与系统自带的命令对比。系统命令被替换时,两者输出会不一致,这是判断系统工具是否可信的低成本方法。
三、判断:这个进程该不该杀
| 特征 | 正常进程 | 可疑信号 |
|---|---|---|
| 可执行文件路径 | /usr/bin、/usr/sbin、/usr/local/bin | /tmp、/dev/shm、/var/tmp、隐藏目录(以点开头) |
| /proc/PID/exe | 指向真实文件 | 显示 (deleted),说明运行后自删 |
| 启动用户 | 与服务匹配(nginx、mysql 等) | root 启动了不明程序,或 Web 用户启动了 shell |
| CPU 占用模式 | 随业务量波动 | 恒定高占用,与业务量无关 |
| 网络连接 | 连接已知业务地址 | 长连接到陌生境外 IP 或非常规端口 |
| 父进程 | systemd(PID 1) 或对应服务 | 父进程是 Web 服务、或父进程已消失成为孤儿 |
| 命令行 | 可读、与文档一致 | 经过 base64 编码、随机字符串命名 |
四、标准处置流程
- 先保住现场,不要急着杀进程。用
kill -STOP PID冻结它,而不是kill -9。冻结后进程停止活动但内存与文件句柄保留,便于取证;直接杀掉可能触发自毁逻辑或由守护进程立刻拉起。 - 保存证据。把
ps auxf、ss -antp、/proc/PID/下的关键信息、相关日志(auth.log、Nginx 访问日志、journalctl)打包另存到机器外,并记录时间线。 - 确认影响范围。这台机器能访问哪些内网资源?上面有没有数据库凭证、SSH 私钥?同一网段其他机器是否也有类似迹象?必要时先隔离网络。
- 定位入侵入口。查看
last、lastlog找异常登录;查 Web 访问日志找可疑请求;查服务版本是否有已知漏洞。找不到入口的清理等于白做。 - 清理持久化。恶意进程之所以杀不死,是因为有守护机制。必须先清掉定时任务、systemd 服务、启动脚本、SSH 公钥、异常账号,再终止进程。
- 终止进程并删除文件。清理完持久化后,
kill -9终止进程,再删除可执行文件(注意先chattr -i解除不可修改属性)。 - 修补入口并改密码。升级服务版本、关闭暴露的端口、重置所有账号密码与密钥。
- 持续观察 72 小时。清理后至少观察三天,确认 CPU、带宽、连接都回到基线且没有复发。
- 复盘与加固。把时间线、入口、影响写成报告,补充监控与防护,必要时接入主机安全 agent。
五、清理持久化的具体动作
这是最容易漏、也最决定成败的一步。恶意程序常见的驻留位置有下面几处,必须逐一检查。
# 1. 检查 systemd 服务(重点看陌生的、近期创建的) systemctl list-unit-files --type=service --state=enabled ls -la /etc/systemd/system/ systemctl cat 可疑服务名 # 停止、禁用并删除 systemctl stop 可疑服务名 systemctl disable 可疑服务名 rm -f /etc/systemd/system/可疑服务名.service systemctl daemon-reload systemctl reset-failed
# 2. 检查各类定时任务
crontab -l
crontab -l -u root
ls -la /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/
ls -la /var/spool/cron/crontabs/
cat /etc/crontab
# 3. 检查开机启动脚本与 profile
cat /etc/rc.local
ls -la /etc/profile.d/
cat /etc/profile
# 4. 检查 SSH 公钥(最常见的后门位置)
cat ~/.ssh/authorized_keys
cat /root/.ssh/authorized_keys
ls -la /home/*/.ssh/
# 5. 检查异常账号与 UID 0 账号
awk -F: '$3 == 0 {print $1, $3, $7}' /etc/passwd
last -n 30
lastlog
# 6. 查找近期被修改的可疑文件(例如 3 天内 /tmp 与 /usr/bin 下的文件)
find /tmp /dev/shm /var/tmp /usr/bin -type f -mtime -3 -ls 2>/dev/null | head -40
# 解除文件不可修改属性后再删除(chattr +i 是常见的自我保护手段) lsattr /usr/bin/可疑文件 chattr -i /usr/bin/可疑文件 rm -f /usr/bin/可疑文件 # 确认进程已终止且不再被拉起 ps -eo pid,ppid,user,cmd | grep -i 可疑关键字 | grep -v grep ss -antp | grep -i 可疑关键字
六、什么时候应该直接重装
并非所有情况都值得花时间清理。出现下面任一情况时,重装系统并重建环境反而是更理性、更安全的选择:
- 检测到内核态 rootkit,或关键系统命令(ps、ls、netstat、sshd)已被替换且无法确认替换范围。
- 无法定位入侵入口,清理后短时间内复发。
- 机器上存有高敏感数据,且无法证明数据未被访问。
- 系统版本过旧、补丁缺失严重,修复成本高于重建。
即便决定重装,也要先把数据备份到隔离环境、保留磁盘镜像作为证据,并在新环境上确认没有沿用被污染的备份(尤其是 Web 代码目录里的后门文件)。重装后必须从干净镜像起步,先把补丁打全、端口收敛到位,再部署业务。
七、事后加固清单
- 关闭所有非必要端口:数据库、Redis、Docker API 一律不暴露公网,这是入侵最主要的入口。
- SSH 改为密钥认证:
PermitRootLogin no、PasswordAuthentication no,配合 fail2ban 自动封禁。 - 补齐系统与组件补丁:优先修复 Web 服务、中间件、CMS 与插件的已知漏洞。
- 用 auditd 监控关键文件:
auditctl -w /etc/crontab -p wa -k cron_change、-w /root/.ssh/authorized_keys -p wa -k key_change。 - 部署主机安全 agent 与文件完整性检测:让异常进程和关键文件变更能被自动发现,而不是靠人肉巡检。
- 收敛账号与权限:清理无用账号,服务用独立非 root 用户运行,sudo 权限最小化。
- 建立备份与恢复演练:保证在最坏情况下能在小时级恢复业务,这是最后一道防线。
小结
处理被植入恶意进程的服务器,正确顺序是"冻结取证 → 定位入口 → 清理持久化 → 终止进程 → 修补加固 → 持续观察"。其中最关键也最容易出错的一步是先清理持久化再杀进程——顺序反了,进程会立刻被守护机制拉起,你会陷入反复清理的循环。工具上,ps -eo 与 ss -antp 用于发现,/proc/PID/ 用于取证,rkhunter、chkrootkit、unhide 用于验证系统是否可信。而当 rootkit 已深入内核或入口始终无法定位时,果断重装并重建环境,比在污染的系统上修补更划算。
避坑建议
- 不要一上来就
kill -9:先kill -STOP冻结,保留内存与文件句柄,避免触发自毁或丢失证据。 - 不要只杀进程不查持久化:定时任务、systemd 服务、SSH 公钥是三大复活通道,必须全部清干净。
- 不要信任被污染的系统命令:用静态
busybox或独立工具交叉验证,ps输出"太干净"本身就是危险信号。 - 不要在找到入口前宣布处置完成:入口不堵,清理得再干净也会在几天内复发。
- 不要忘记排查同网段其他机器:内网横向移动很常见,只处理一台往往不够。
- 不要忘记改密码和轮换密钥:机器上保存过的一切凭证都应视为已泄露,包括数据库密码、API 密钥、SSH 私钥。
- 不要在业务高峰期直接断网隔离:生产环境需先与业务方协调,评估影响后再决定隔离方式,避免处置动作本身造成更大损失。
企业QQ咨询




