干安全这一行,最不缺的就是告警,但挖矿病毒这玩意儿,总能在你值班的深夜把CPU拉到满,把业务拖到报警。前阵子处理了一起典型的内网Linux服务器挖矿事件,从监控告警到定位、清除、加固,前后折腾了一整晚。这篇把完整过程记录下来,包括那些藏在细节里的门道和踩过的坑,给同样要面对这些问题的朋友做个参考。
先说结论:挖矿病毒单看技术含量并不高,但真正麻烦的是它背后的持久化机制和自动化传播链条。矿机进程本身很容易杀,难的是找出那个不断把它拉起来的“母体”,以及修复被攻击者利用的入口。这次事件里,我一开始也差点只盯着高CPU进程去打,结果清理完不到五分钟就复活了。逼着我把排查思路从“找矿机”切换到“找入口、找持久化、挖传播链”,才真正把问题按住。整个过程里,有几个判断和操作顺序很关键,下面按时间线拆开讲。
1. 事件起点:一条不寻常的CPU告警
1.1 告警是怎么来的
那天晚上大概十一点多,监控平台弹出一条告警:某台内网业务服务器的CPU使用率持续五分钟超过98%。第一反应是跑批任务或者数据同步,但看了下时间窗口,业务早就过了晚高峰,这个时候不应该有这么大的计算量。登录服务器之后用top看了一眼,一下子就发现了异常:一个名叫kworker的进程,CPU占用接近900%(多核环境下单进程占到接近满核)。
懂Linux的朋友应该知道,kworker是内核工作队列的内核线程,正常情况CPU占用非常低,而且不会出现在用户态进程的列表里。如果top里看到一个名字类似内核进程的东西CPU拉满,第一反应就应该是伪装。用ps -ef看了下完整命令行,进程路径指向/tmp/.X25-unix/xxx,这种隐藏目录加随机名的组合,基本就是挖矿木马的老套路。
1.2 第一反应:先别急着杀
应急响应的第一条原则,我始终觉得是:先保全证据,再谈处置。当时我做的第一件事不是kill,而是先在云控制台给这台服务器打了个快照,然后又用主机侧的安全agent导了一份内存镜像。这一步看似多花了几分钟,但后面分析样本和追溯攻击路径时,全靠这份原始数据撑着。打完快照之后,才从网络层面把服务器单独隔离到一个应急VLAN,保留服务器本身可登录,但禁止它和业务网段继续通信。
这里有个很容易被忽略的点:不要一看到外联矿池IP就急着拔网线。拔了网线,矿机进程可能因为你断网而自动退出,或者被守护进程发现异常后自毁,样本没抓到,痕迹没保留,纯粹变成一次“盲清”。最好是先让它继续跑着,把网络连接、进程行为、文件落地这些数据尽可能记录下来,再动手隔离和处置。
1.3 初步定性:这算哪一类挖矿病毒
从最初的表象看,进程隐藏在/tmp下的隐藏目录、名字伪装成内核线程、外联端口不常见,这几个特征综合下来,基本可以判断是门罗币矿机外加一个负责传播的感染模块。这类组合在真实环境里非常常见:矿机本身负责赚钱,感染模块负责横向扩散。矿机进程只是一个“打工人”,真正难缠的是它背后的那个“包工头”——如果只清理矿机进程,不把感染模块和持久化任务找出来,几分钟后就会有新的矿机被拉起来。
所以从这一步开始,我的排查目标就从“杀掉那个高CPU进程”,变成了“找出整个感染链”。后面的每一步排查,都在围绕这个目标展开。
2. 全面排查:如何把藏起来的矿机挖出来
2.1 进程排查:看清伪装者
进程排查是第一步,也是最容易出错的一步。只看top和ps远远不够,因为高级一点的木马会做进程名伪装、命令行清空、甚至rootkit隐藏。常规检查我一般按这样的顺序来:
top先看CPU和内存排行,锁定额外高的PIDps -ef和ps auxf看完整命令行和父子进程树ls -l /proc/PID/exe查看可执行文件的真实路径,这是是否伪装的关键cat /proc/PID/cmdline查看启动参数,攻击者可能在这里塞入矿池地址cat /proc/PID/environ查看环境变量,有时能看出投放方式
实际遇到的情况是:那个进程名字叫ksmtuned,看起来像是KVM虚拟机里用来整理内存的内核服务,不熟悉的人很容易放过去。但ksmtuned正常情况CPU占用很低,而且它出现的那台服务器根本不是虚拟机。顺着ls -l /proc/PID/exe一看,真实路径指向/usr/lib/.libs/xxx,这就彻底露馅了。
还有一个细节容易被忽略:看进程的PPID。如果异常进程的PPID是1,说明它已经被init进程收养,真正的启动者可能是一个脚本;如果PPID指向另一个异常进程,那就要用pstree -ap把整条进程树拉出来看。我当时顺着进程树往上翻,发现还有一个父进程在后台守着,这正是后面“清完又复活”的原因之一。
2.2 网络排查:矿池外联是铁证
进程层面的怀疑还需要网络层面来实锤。网络排查我习惯三件套:
ss -tunap查看当前所有TCP/UDP连接和对应进程netstat -antop做备查,部分老系统只有这个lsof -i :port反查某个端口的占用进程
挖矿病毒的外联特征比较明显:目标IP大量集中在境外,端口常走4444、5555、3333、14444这类非标准端口,而且连接是持续性的,不像正常业务请求那样频繁断连。当时我用ss看到一个异常进程持续连接某个IP的5555端口,顺手用tcpdump抓了几分钟包,数据包里能看到明显的矿池协议交互特征。有些加密流量版本还会用TLS伪装,但矿池协议的握手特征依然能从包长和连接行为上辨认出来。
这里要提醒一句:单靠封禁矿池IP不是长久之计。现在很多挖矿木马都带代理池或轮换域名功能,一个IP被封了,过几个小时就会换新IP继续跑。网络层面的动作只能是应急止血,真正解决问题还得靠进程、文件、持久化层面的清理。你要是只封IP不删母体,等于是给病毒换了个出口,该挖还是挖。
2.3 文件排查:木马的藏身之处
挖矿病毒对目录的选择有很强的偏好,基本集中在这些地方:
/tmp和/var/tmp:老牌藏身处,空间大、权限随意/dev/shm:内存文件系统,重启消失但平时不容易被常规扫描发现/usr/lib、/usr/sbin:混在系统库里,迷惑性很强/etc下以隐藏文件形式存在,比如/etc/.java
我当时的排查方式是先按修改时间找最近变动的文件:
find / -mtime -3 -type f \( -name "*.sh" -o -name "*.*" \) -newer /tmp 2>/dev/null | head -100然后重点看异常目录下的内容。/tmp/.X25-unix这个目录里除了矿机二进制,还有一个run.sh脚本,脚本内容就是启动矿机、设置LD_PRELOAD、写计划任务。这个脚本非常关键,因为它直接串起了整个感染链条。
另外别忘了用lsof查看“已删除但还在运行”的文件。木马有时会删除自身落盘文件,但进程还在内存里运行,这时候/proc/PID/fd下面还能看到原始文件描述符,可以用来恢复样本做特征分析。这一步对后续写检测规则、判断传播方式很有价值。
2.4 持久化排查:为什么狠心杀掉还会复活
这是整个应急响应里最核心的一环。挖矿病毒的持久化手段来来回回就那几类,但每类都值得认真查一遍:
- 计划任务:
crontab -l查当前用户,/etc/cron.d/、/etc/crontab查系统级任务 - 系统服务:
/etc/systemd/system/下新增的service文件 - SSH公钥后门:
~/.ssh/authorized_keys里多了不认识的公钥 - 动态库劫持:
/etc/ld.so.preload被写入恶意so路径,导致所有新起进程都加载恶意代码 - 启动脚本:
/etc/rc.local、/etc/profile.d/下的脚本
我当时检查系统级计划任务时,在/etc/cron.d/下发现一个名为update的文件,内容是一段bash -c脚本,每五分钟从指定URL拉取一段脚本执行。这个就是复活机制的核心。不把这条计划任务删掉,你前面杀多少次矿机进程都没用,五分钟一到,脚本自动下载新样本,再次拉起矿机。
我还顺手检查了/etc/ld.so.preload,发现里面果然被写了一个so文件的路径。这个更阴险,它不直接启动矿机,而是把所有新进程都注入一遍恶意代码,等于你每次执行ls、ps,都在帮它打工。清理时这个文件必须重点处理。
2.5 日志与账号排查:找出来路
光知道“怎么活的”还不够,还得知道“怎么进来的”。于是我翻了/var/log/auth.log和/var/log/secure,发现事发当天凌晨三点左右,有一段时间连续几百次SSH登录失败,来源IP不固定,明显是暴力破解扫描。紧接着就有一次成功登录记录,用的还是root账号。这基本锁定了入侵路径:SSH弱口令加暴力破解。
继续往下挖,检查了/root/.bash_history,看到了攻击者登录后执行的命令:下载脚本、赋权、执行、清理痕迹。还看了/etc/passwd,确认有没有新增的uid=0账号,检查/etc/sudoers有没有被加白名单。这些动作组合起来,就可以还原整个攻击链路了。
还有一点要养成习惯:把登录日志、历史命令、计划任务、authorized_keys这几个文件都留一份副本,不光是为了这一次处置,也是为后续溯源留证据。很多时候应急响应结束不代表事情结束,你还得做复盘报告,这时候日志副本就是最有力的证据。
3. 清除与恢复:一次干净的收尾
3.1 清除流程:先断网,再停止,后删除
清理操作最忌讳一上来就kill -9。稳妥的执行顺序是这样:
- 隔离网络,封禁对外连接,让矿机无法继续上链
- 保存关键证据:进程内存镜像、恶意文件副本、日志副本
- 删除计划任务和自启动项,切断复活机制
- 结束异常进程,优先
kill -9 - 删除恶意文件,包括隐藏目录、so文件、公钥
- 修改被暴露的密码和密钥
- 修复被利用的漏洞,再考虑恢复业务
当时执行完计划任务清理和进程清理后,我用的是一组很直接的操作:
# 清理计划任务 rm -f /etc/cron.d/update crontab -r # 终止挖矿相关进程 pkill -f xmrig kill -9 4567 # 删除恶意文件和目录 rm -rf /tmp/.X25-unix rm -f /usr/lib/.libs/xxx rm -f /etc/ld.so.preload # 检查并清理可疑公钥 cat /root/.ssh/authorized_keys这里有三个细节值得注意。第一,pkill -f xmrig这种按进程名匹配的方式不一定可靠,因为很多变种会把进程名改成随机字符串或者伪装系统进程名,所以最终的清理还是得靠/proc/PID/exe定位到的真实路径。第二,有些木马会做进程双守护,两个进程互相拉起,这时候只杀一个必然复活,最好先断网再同时杀掉。第三,rm -f /etc/ld.so.preload删掉之后,所有已加载恶意so的进程并不会自动卸载,需要重启相关服务或直接重启系统才能彻底干净。
3.2 清理之后:验证是否还有漏网之鱼
清理完不要急着上线,先做一轮复查。我当时按这个清单过了一遍:
top确认CPU恢复正常,没有快速飙升的进程ss -tunap确认没有未知外联crontab -l加ls /etc/cron.d/确认计划任务为空- 检查
/proc下每个PID的exe是否都是正常路径 - 关掉异常端口后观察半小时,看是否有新外联出现
这里说个我踩过的坑:第一次清理完后,我观察了十分钟没异常,正准备恢复业务,结果突然又看到一条新外联。这说明母体还没找到。后来通过安全agent的进程树关联功能,发现一个名字非常正常、类似sshd的服务进程居然在频繁拉起子进程,仔细一看,那个sshd的二进制文件已经被替换过了,原始服务早就被攻击者用恶意版本覆盖。这种替换系统进程的情况,单靠人工排查非常容易漏掉,因为进程名、端口、行为看起来都和正常服务没区别。
面对这种情况,核心服务器上的文件完整性监控就特别重要。如果你有部署过AIDE或auditd,直接对比基线就能秒级发现哪个系统文件被篡改。没有的话,就只能靠rpm -Va这类包管理器自带的验证命令来排查了。
3.3 被篡改的文件:修复而不是重装
发现系统文件被替换后,很多人第一反应是重装系统。但如果你能确认篡改范围不大,修复通常是更快的路径。以SSH服务被替换为例:
# 查看sshd文件属于哪个软件包 rpm -qf /usr/bin/sshd # 验证包内文件是否被改动 rpm -V openssh-server # 重新安装修复 yum reinstall openssh-server -y如果是Debian系:
dpkg -S /usr/bin/sshd apt-get --reinstall install openssh-server修复之后别忘了重启服务,再确认进程和端口恢复正常。如果脏文件已经扩散到很多目录,或者你根本没耐心一个个验证,那就备份数据重装系统,这也是最彻底的方案。但重装前必须想清楚:如果最初被攻破的原因是SSH弱口令,重装之前不处理这个弱点,上线第二天可能又被爆破进来,等于白干一次。
4. 常见问题与排查技巧实录
4.1 为什么清了又复发
这是应急响应里被问得最多的问题。结合经验来看,清完又复发基本逃不出下面几个原因:
第一,持久化机制没清干净。计划任务、systemd服务、ld.so.preload、rc.local,只清了一个,另一个还在,五分钟到点照样拉起来。第二,内网里有其他被控机器。你清了这台,那台还在不断向内网投递样本,过一会儿又感染回来。这种情况需要在内网做一次大范围排查,不能孤立看待单台机器。第三,攻击者留了后门,比如authorized_keys里有攻击者的公钥,清理完木马,攻击者还是可以随时登录,再次植入。
碰到复发的情况,先别急着怀疑自己清理不彻底,把排查范围扩大,确认是不是横向传播的问题。
4.2 进程隐藏手段
做应急响应时间久了就会发现,挖矿木马在“藏”这件事上越来越用心。常见的隐藏手段包括:
- 进程名伪装成内核线程或系统服务,比如
kworker、ksoftirqd、ksmtuned - 使用rootkit模块,让恶意进程彻底不出现在
ps输出里 - 进程启动后立刻删除自身可执行文件,让你在文件系统里找不到样本
- 命令行参数清空,
ps看不到矿池地址
针对这些手段,我习惯用/proc目录和ps输出做交叉比对。如果发现某个PID只存在于/proc列表、但ps和top都看不到,那大概率有rootkit。还可以用rkhunter这类工具做辅助扫描,但核心还是手工验证。
另一个技巧是直接看/proc/PID/status里的进程名和/proc/PID/exe的真实路径是否一致。攻击者能改comm字段,但改不了exe指向的二进制文件路径,这个差异一眼就能看出来。
4.3 排查时容易踩的坑
几次数下来觉得最要命的坑就这几个:
- 第一时间杀进程,把证据搞丢了。一定先快照、再隔离、后处置
- 只清理矿机,漏掉母体。矿机是定期被拉起的,只杀矿机就是治标不治本
- 只封矿池IP,不封内网弱口令。攻击者换IP再进来很容易
- 忽略
/dev/shm、/var/tmp这类隐蔽目录。常规扫描根本不会看这里 - 修改密码只改root,不检查业务账号和API密钥。攻击者拿到的不止一个入口
每次排查过程中我都会顺手把这些点过一遍,宁可多花十分钟检查,也不要第二天早上顶着两个黑眼圈再处理一次。
5. 复盘与后续加固:应急响应不能只做一半
5.1 攻击路径复盘
等所有清理动作做完,我习惯把整个事件的时间线拉一遍。这件事的时间线大概是这样的:
- 凌晨3:00左右:外部IP开始对服务器SSH端口进行连续爆破
- 3:10左右:攻击者使用弱口令成功登录root账号
- 3:15到3:20:攻击者下载执行脚本,写入计划任务和启动项
- 3:25左右:矿机进程开始运行,CPU逐步升高,直到触发监控告警
从这条时间线能看出,入侵窗口非常短,从爆破成功到矿机运行只用了十几分钟。如果当时有主机入侵检测系统做了暴力破解告警,提前拦截,后面的事情根本不会发生。复盘的意义就在这里,它不是走形式,而是帮团队找出真正的控制点,避免同样的事情二刷。
5.2 加固清单
应急响应做完,加固必须跟上。我个人最推荐按下面这个清单逐项推进:
- 关闭SSH密码登录,全部改用密钥认证;确实需要密码的场景,加上
fail2ban做爆破拦截 - 修改SSH默认端口,至少不要让
22端口裸露在公网上 - 安全组或防火墙收紧白名单,只放行业务必要端口
- Redis、MongoDB这类中间件开启强认证,禁止无密码监听所有网卡
- 统一账号口令管理,杜绝弱口令
- 部署文件完整性监控工具,比如AIDE或者
auditd,核心文件一改动就告警 - 日志集中收集并保留足够周期,做好溯源基础
- 每半年做一次应急响应演练,把流程跑顺
这里面的核心思路就一句话:把攻击者的入口全部堵死。挖矿病毒没有多高深,几乎都是靠弱口令和未授权访问进来的,把这两条路堵上,大多数挖矿事件都能在源头被切断。
5.3 长效化:把经验变成流程
这次事件处理完之后,我建议团队把它整理成一套应急响应playbook:接到告警后先快照再隔离,按进程、网络、文件、持久化四个维度排查,清除后做验证,最后进入复盘加固。整个流程固定下来后,后面再遇到类似事件,同事照着走就行,处理时间从最初的几个小时缩短到了三十分钟以内。
如果你所在团队还没有类似的预案,真的可以考虑花半天时间把流程沉淀下来。应急响应最大的成本不是技术操作,而是事发后大家手忙脚乱的“讨论时间”。有了一份清晰的检查清单,哪怕经验不足的同事也能有条不紊地处理。
写到这里,想说点个人的真实体会。挖矿病毒看起来技术含量不高,很多人觉得kill掉进程就完事了。实际操作过就明白,难的不是杀进程,而是找出它的复活机制、修复入侵路径、守住内网防线。那次应急响应之后,我最大的感受是:投入精力在平时巡检上,真的比事后补救划算得多。像检查authorized_keys、计划任务、系统关键文件完整性这些动作,顺手就做了,别等告警响了才想起来。遇到类似情况,按这套流程走下来,大概率能少走不少弯路。别急着重装,先把证据留好——矿机可以杀,问题的根,必须挖出来。