资讯动态

Linux服务器CPU飙升?一次挖矿病毒完整排查清理与加固实战

发布时间:2026/9/9 7:31:14 来源:尧图企业网站定制
1月19号下午我正蹲在工位上改一份配置文档手机连着弹了三条告警——生产服务器的CPU使用率已经超过100%load average直接飙到11。登上去第一眼top里有个进程顶着接近4000%的CPU跑名字看着挺正常但我心里已经明白这是挖矿病毒。先说给谁看。如果你管着几台Linux服务器、跑着Docker容器或者公司里有过一台“突然卡死”的机器这篇文章就是写给你的。挖矿病毒这件事早发现早处理其实半小时就能搞定拖到后面要么数据被删、要么被反复种回来那才是真难受。我把这次完整的排查、清理、加固过程整理成一份记录命令都是实际敲过的照着顺序做基本能解决问题。1. 事故初现服务器的CPU为什么会突然失控1.1 现象、告警和第一反应事情发生得很突然但仔细看又觉得“该来的总会来”。我手上的服务器跑了一台Nginx反代、一个内部用的数据库容器平时负载长期在0.3以下。结果下午2点40分左右监控平台先报了CPU超过80%的阈值过了五分钟直接报警到“CPU使用率300%”这数字一看就不对劲——四核机器撑死400%能长期跑300%以上基本只有挖矿干得出来。我当时的操作顺序是这样的先用top -c看了一眼进程列表按CPU排序第一行是一个叫kdevtmpfsi的进程PID不大说明不是老进程像是最近才拉起来的再敲free -m看内存发现可用内存只剩不到200MB正常应该在3GB以上然后uptime确认 load average 已经到了 11.5, 8.3, 5.1呈明显上升趋势。结合这三条基本可以断定是恶意挖矿程序。为什么这么肯定因为正常业务进程不可能在几分钟内把CPU从个位数拉到几百更不可能凭空多出来一个名字都没见过的进程。挖矿病毒有个共同特征启动后立刻利用所有可用的CPU核心跑哈希计算所以它的CPU占用曲线是“陡峭飙升”而不是“平稳上升”和业务流量的表现完全不一样。这里分享一个判断经验如果load average是缓慢上涨比如从1涨到3可能是业务高峰如果是在十分钟内从0.5涨到11优先怀疑挖矿别急着优化代码。1.2 为什么挖矿病毒这么难缠挖矿病毒不是普通的木马它最讨厌的地方在于会“自我修复”和“多重持久化”。很多人在第一步发现进程后直接kill -9杀掉结果不到一分钟又冒出来了原因就是没找到它的启动器。实际上一套完整的挖矿病毒通常包含三个部分挖矿主程序、守护进程、持久化任务。挖矿主程序负责计算特征是CPU占用率高、进程名随机或伪装守护进程负责监视主程序一旦发现主程序被杀就重新拉起来持久化任务则藏在定时任务、systemd服务、启动脚本、容器镜像里负责在服务器重启或容器重建后再次激活。所以清理的时候如果只杀主程序等于只把草割了根还在地底下。这次遇到的还算“正规军”进程名kdevtmpfsi是近年来比较常见的挖矿木马打开/proc/PID/目录能看到二进制文件已经被删掉说明木马本身有反侦察设计——要么让它直接跑在内存里要么会把磁盘上的原始文件删掉藏起来。这种情况下光看进程列表是不行的必须顺着进程找到它的启动链。2. 顺藤摸瓜先揪出进程再顺出整个病毒链2.1 用ps和top锁死可疑进程排查的第一步是确认“到底是谁在跑”。当时我执行了这么几条命令每一条都有它的用意# 查看进程完整命令行和父进程关系 ps auxf | head -50 # 按CPU使用率排序取前20个进程 ps -eo pid,ppid,user,%cpu,%mem,cmd --sort-%cpu | head -20 # 查看某个PID的详细信息 ls -l /proc/12345/exe cat /proc/12345/cmdline | tr \0 ps auxf用树状结构显示进程关键要看父进程是谁。正常的业务进程父进程一般是systemd或者对应的服务管理进程挖矿木马的父进程往往是奇怪的shell比如/bin/sh或者一个临时目录下的脚本。再来说两个容易踩的坑坑一有些木马会伪装成[kworker]、[systemd]这种带方括号的内核线程名看起来像是系统自带的进程。区分方法很简单内核线程的cmd是空的而且没有exe链接真正的恶意进程一定会有可执行的路径。坑二有些挖矿木马会直接把进程名改成你服务器的业务进程名比如nginx、java。这时候不能只看进程名要看它的启动路径和参数。正常nginx跑在/usr/sbin/nginx木马跑在/tmp/nginx或/var/tmp/...路径就能出卖它。我当时锁定的进程比较典型PID大概是1421父进程是1号进程systemd但它实际的可执行文件/proc/1421/exe指向了一个已经删除的路径说明二进制文件启动后立刻自删了。这种情况下kill -9可以直接杀掉当前进程但如果没有处理父进程和启动链重启后还会再起来。2.2 网络连接矿工总是要“向外说话”的挖矿程序不管怎么伪装它总要连接矿池才能干活。矿池连接有两个特征一是目标端口固定常见的挖矿端口有3333、4444、5555、7777、14444、45560等二是连接方向是出站连接也就是服务器主动连出去。此时的排查命令是# 查看所有建立的TCP连接 ss -antlp # 定位到可疑进程的网络连接 lsof -p 1421 # 按连接数排序快速找异常外联 ss -ant | awk {print $5} | sort | uniq -c | sort -rn | head -20在ss -antlp的输出里那个进程已经连上了几个境外IP的44xx端口。这个特征非常明显因为正常业务不会突然建立这么多高端口外联。确认后我立刻用防火墙把这几条连接限制住防止它在清理过程中继续挖矿# 临时封禁矿池IP以实际IP为准 iptables -A OUTPUT -d 192.168.1.5 -j DROP # 或直接封禁进程出网需要owner模块 iptables -A OUTPUT -m owner --pid-owner 1421 -j DROP这里有个细节第一条封IP的命令只能阻止已知矿池地址木马可能会切换到备用地址第二条按进程封禁则更彻底直接让它无法建立新连接。不过--pid-owner在进程被kill后会失效所以这只是过渡手段不能当长期方案。排查网络时还要注意一个坑有些挖矿木马会使用curl或wget下载后续攻击模块如果服务器的出网流量很大光靠ss看到的连接列表可能会非常长。建议加上grep -E ESTAB|SYN-SENT只看已建立和正在尝试连接的条目缩小范围。2.3 文件系统和容器木马最喜欢藏在哪里进程和网络确认之后就要查文件了。挖矿木马写盘的位置有很强的规律性基本都是临时目录或可写目录很少有藏到/usr/bin下面的因为权限不够。最常见的藏身点包括/tmp/var/tmp/dev/shm/usr/lib//usr/libexec/$HOME/.config/容器的/var/lib/docker/overlay2/里那台服务器上我在/tmp下面找到了一个删除时间很蹊跷的.img文件名字不显眼大小却有30多MB还带着执行权限。用file命令一看是Linux ELF可执行文件——一个伪装成镜像文件的可执行程序这基本就是木马本体。再往深处挖/etc/crontab里多了一行每分钟执行的定时任务内容指向一个外部脚本地址/root/.bashrc末尾也被追加了一段拉取代码。这两个是典型的持久化手段定时任务保证“机器重启后还能再拉起”.bashrc保证“root登录时自动触发”。还有一个地方必须查/etc/ld.so.preload。这是Linux的动态链接库预加载文件挖矿木马经常往里面写一个.so文件这样所有程序启动时都会先加载它的恶意代码杀都杀不干净。这次运气不错没有在ld.so.preload中发现异常但排查时一定不能漏掉这一步。服务器上跑着Docker所以还需要检查容器侧# 查看所有运行中的容器 docker ps -a # 查看容器的启动命令和挂载情况 docker inspect 容器名 | grep -A5 Cmd # 查看容器资源占用 docker stats --no-stream检查后发现有一个数据库容器从redis镜像启动但它内部多跑了一个/bin/sh从网上下载脚本的动作。这说明容器是用挂载方式打开了外部目录被攻击者当跳板用了后面清理时这个容器必须清理掉。3. 清除实操从容器、定时任务到内存进程一层一层拆干净3.1 按顺序操作不能上来就kill很多新手遇到挖矿病毒第一个动作就是kill -9。这个动作在“单独一台测试机”上没问题但在生产环境就是大忌因为你不知道它和多少个其他进程联动。正确的顺序是先隔离、再取证、后清理。我当时的具体流程立刻把服务器从内网业务流量中摘除或者最差也要把相关容器的网络断开防止攻击者远程操作保存一份进程、网络、文件的快照留作后续取证用把可疑容器暂停而不是直接删除方便检查镜像是否被污染最后才是清理进程、删除文件、清除定时任务。停容器这一步很关键。用docker pause而不是docker stop因为pause会冻结容器内所有进程但不会破坏容器的文件系统这样才能保留现场。如果直接docker rm容器里可能还有正在运行的恶意脚本删的时候反而可能触发它的反清理机制。隔离完成后开始动进程。我用两条命令清理# 先冻结恶意进程如果没有容器管理的话 kill -STOP 1421 # 确认没有子进程在跑后再彻底杀掉 kill -9 1421这里用kill -STOP有一个好处它能先暂停进程让我有时间检查这个进程有没有在写文件、在连接网络。如果进程正在持续创建子进程kill -9杀掉主进程的瞬间子进程会立刻接管冻结之后再杀就能避免这种“杀了一个出来两个”的情况。3.2 清理定时任务、系统服务和SSH后门进程清理掉不代表结束真正的重头戏是处理持久化。我逐项排查并清理了这些地方定时任务类# 查看当前用户的crontab crontab -l # 查看系统级定时任务 cat /etc/crontab ls -la /etc/cron.d/ ls -la /etc/cron.hourly/ ls -la /var/spool/cron/ # 删除恶意定时任务以具体行为准 crontab -e当时发现了一条指向http://xxx.xxx.xxx/k.sh的定时任务每隔一分钟执行一次。我先把这条任务从crontab里删除再把下载到本地的脚本文件也删了。这里提醒一下只删crontab里的条目还不够脚本本身可能被伪装成.sys或.log文件藏在/tmp、/var/tmp要找到并清理干净否则下次定时任务被重新添加时还会执行。systemd服务类有些挖矿病毒不依赖crontab而是直接注册一个systemd服务这样开机自启更隐蔽。检查命令# 查看所有自启动服务 systemctl list-unit-files --typeservice | grep enabled # 查看最近修改过的service文件 find /etc/systemd/system -name *.service -mtime -7 -exec ls -l {} \; # 停止并禁用恶意服务 systemctl stop xxx.service systemctl disable xxx.serviceSSH后门类攻击者往往会往authorized_keys里塞一个自己的公钥这样服务器怎么重装系统只要密钥对还在他就能通过SSH重新进来。检查cat ~/.ssh/authorized_keys # 正常情况应该只有你自己的公钥多出来的都删掉这次排查时/root/.ssh/authorized_keys里多了一个陌生的公钥明显不是我们团队任何人的。我删掉了那行并且检查了/etc/ssh/sshd_config有没有被修改过确认没有异常才放心。3.3 处理被污染的容器和镜像避免“清完又复发”容器侧的清理更麻烦。当时那个数据库容器已经被植入恶意代码如果只把容器里的恶意进程杀掉下次容器重启还是会从同样镜像里拉起来。所以我的做法是先保留docker inspect输出和镜像的哈希值留档用docker commit给当前容器状态做个快照备份万一以后要排查删除这个容器docker rm -f 容器名把对应镜像也删掉从可信源重新拉取并重建容器。这里说一个很多运维会踩的坑有些人的业务镜像本来没问题但攻击者进入容器后往容器里写入恶意外挂进程然后docker commit把容器提交成新镜像。你不看历史记录根本不知道镜像已经被污染了。所以清理挖矿病毒时容器要么重建要么就从备份恢复绝不能只“杀进程”就完事。再补充一个排查容器挂载的思路。攻击者能进入容器很大概率是因为容器把宿主机的某个目录挂载进去了比如/:/mnt这种操作。检查所有容器的挂载项for c in $(docker ps -aq); do echo $c ; docker inspect $c --format {{range .Mounts}}{{.Source}} - {{.Destination}}{{println}}{{end}}; done如果看到容器把宿主机的根目录、/etc、/root挂载进去了那这台宿主机基本上等同于裸奔。这次恢复容器时我把这种风险目录全部去掉了业务目录的挂载也改成了只读能读不能写。4. 复盘与加固让挖矿病毒没有第二次机会4.1 攻击入口分析它是怎么进来的病毒清理完了但如果不搞清楚“入口在哪”下一次只是时间问题。我在清理过程中收集到的线索有服务器上跑着Docker服务宿主机的2375端口对外开放还有一台Redis服务绑定了0.0.0.0而且没有设置密码。这两个都是挖矿病毒最经典的入口。先看Docker 2375端口。Docker API如果直接暴露到公网攻击者可以通过API执行任何容器命令包括启动一个挂载根目录的恶意容器、往宿主机写文件。这次虽然没有在宿主机上发现容器逃逸的迹象但入口显而易见非常可疑。再看Redis未授权访问。Redis在默认配置下会把服务绑定到127.0.0.1但有些安装教程为了图省事直接改成0.0.0.0又不开认证。攻击者就可以用Redis写文件到指定目录常见手法包括往/var/spool/cron/写定时任务、往.ssh目录写公钥、往web目录写webshell。这次排查时我的/root/.ssh/authorized_keys里确实多了一把公钥手法就是通过Redis未授权写入的。所以这次事件的完整攻击链大概率是攻击者扫描公网发现Redis端口开放且无密码连接Redis利用未授权写入SSH公钥或写定时任务成功拿到root权限后下载挖矿木马并设置为持久化清理时发现攻击者还留了SSH后门确保能被再次控制。4.2 日常加固清单把这些口子一个个堵死经过这次折腾我把服务器重新加固了一遍。这里列一份可以直接参考的清单每一条都对应着这次踩到的坑Redis安全修改配置文件把bind 127.0.0.1恢复回来不做外网监听设置强密码并在配置文件中启用requirepass如果必须对外提供服务用防火墙限制来源IP白名单。Docker API安全绝对不要把Docker API暴露到公网本地使用Docker时通过socket访问不加-H tcp://0.0.0.0:2375参数远程管理Docker必须启用TLS双向认证这事不能偷懒。SSH安全只保留自己的公钥清掉所有不认识的authorized_keys修改sshd_config禁止密码登录只允许密钥登录高风险环境建议加上fail2ban暴力破解直接拉黑。防火墙与最小权限服务器默认拒绝所有入站只放行明确需要的端口容器和进程都以最小权限运行不给不必要的root权限定时任务、系统服务清单每周检查一次。这些加固做完至少同类攻击手段再进来会困难得多。4.3 监控与自动化下次能不能早点发现经历了几次挖矿病毒清理我最大的感受就是如果等到CPU告警才处理说明已经晚了。真正靠谱的做法是建立一套“异常发现”机制让问题在早期就暴露。我目前用的方案分三层第一层系统指标监控。用Prometheus node_exporter采集CPU、内存、磁盘、网络等指标配上告警规则。比如CPU持续5分钟超过80%、连接数超过正常基线的3倍就触发告警。这一层能解决“系统什么时候开始变的”的问题。第二层进程与文件监控。关键路径比如/tmp、/var/tmp、/etc/crontab、/root/.ssh做哈希变更监控检测到文件变更就告警。对于进程可以设置一个简单的定时任务记录进程列表用diff对比前后的变化发现新进程就通知。第三层日志审计。把SSH登录日志、Docker事件日志、Redis日志统一收集到日志平台重点监控“登录失败次数激增”“非工作时间登录”“容器创建成功”这些事件。一个简单的监控脚本示例用来检测非白名单进程#!/bin/bash # 定义信任的进程名单按需修改 ALLOWED_PROCsystemd|nginx|mysqld|redis-server|sshd|bash|ps|grep|top|docker # 获取当前所有非内核进程去掉命令中的路径后判断 ps -eo comm | grep -v -E $ALLOWED_PROC | grep -v ^\[.*\]$ | sort -u /tmp/current_proc.txt # 和上一次记录对比输出新出现的进程 if [ -f /tmp/last_proc.txt ]; then diff /tmp/last_proc.txt /tmp/current_proc.txt | grep ^ | awk {print 检测到新进程: $2} fi cp /tmp/current_proc.txt /tmp/last_proc.txt这个脚本逻辑不复杂胜在简单直接配合crontab每隔一分钟跑一次有新增进程就会在日志里留下记录。比完全依赖监控平台要快得多。关于告警还有一个经验告警阈值要设成“可疑就报警”而不是“确认是事故才报警”。挖矿病毒的特点是CPU从正常到跑满可能只需要几十秒如果你阈值设置成“CPU连续5分钟超过90%”才报警那等你收到通知再登录服务器木马早就把矿挖起来了持久化也做好了。清理完挖矿病毒之后我在服务器上放了一个彩蛋给所有关键文件加了隐藏属性不过这里不展开等下次遇到情况再说。只能说经过这次事件我的运维习惯改了不少——每台新服务器上线前必做一次安全加固每个端口必须说明用途每台机器必须接监控。麻烦是麻烦一点但省下来的是半夜爬起来清木马的精力和头发。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价