一、现象:凌晨告警,CPU 飙满
某个凌晨,监控连续推送高负载告警:一台线上业务机 CPU 长时间满负荷,SSH 登录卡顿,uptime 显示负载远超核数。执行 ps aux --sort=-%cpu,排在最前的不是任何业务进程,而是几个陌生名称的可执行文件(典型的挖矿木马,如 kdevtmpfsi 一类),常驻内存、互相拉起。再看 crontab -l,被写入了一条每分钟执行一次的定时任务,内容是从远程地址下载脚本并重新拉起挖矿程序。这台机器不只是被占用资源,攻击者已经拿到了持久化的命令执行入口。
挖矿只是表象。能写 crontab,就意味着攻击者能以当前用户(通常是 root)身份执行任意命令。此时"杀掉进程"没有意义:不切断入口、不清理持久化、不轮换凭据,进程几分钟后就会回来。
二、根因分析:三件事没做对
回溯配置,问题收敛到 Redis 的三项基础防护。它们彼此独立,但只要一项失守,未授权访问就可能成立。
| 配置项 | 危险状态 | 安全状态 | 后果 |
|---|---|---|---|
bind |
注释掉或写成 0.0.0.0 |
127.0.0.1 或内网地址 |
Redis 暴露在所有网卡 |
protected-mode |
no |
yes(默认) |
失去无密码时的非本机兜底 |
requirepass |
未设置或弱口令 | 高强度随机口令 | 任意客户端可直接读写 |
bind未限制。 早期 Redis 默认监听所有网卡,不少环境为图方便把bind 127.0.0.1注释掉,等于把 6379 挂到了公网可达的接口上。protected-mode被关闭。 它是 Redis 在没有密码、又未绑定地址时的最后屏障,一旦设为no,就不再拦截来自非本机的请求。requirepass缺失或形同虚设。 未设密码时任何人redis-cli -h <ip>即可连接;即便设了,用123456、redis这类可猜口令也约等于没有。
一旦拿到未授权连接,攻击者可用 CONFIG SET dir / dbfilename 配合 SAVE 把任意文件写盘(例如写入 authorized_keys 或 crontab),也能通过主从复制、MODULE LOAD 直接执行命令。全程无需暴力破解,公开工具即可完成。
三、排查命令清单
入侵已经发生,先固定证据再动手清理。以下按"确认暴露面 → 定位异常 → 查看持久化"的顺序执行:
# 1. 确认 Redis 是否对外监听、能否无密码连接
redis-cli -h <目标IP> -p 6379 ping
redis-cli -h <目标IP> config get bind protected-mode requirepass
# 2. 查看端口监听与连接情况
ss -lntp | grep 6379
netstat -anp | grep 6379
# 3. 定位异常进程与资源占用
ps aux --sort=-%cpu | head -20
ps -ef | grep -E 'kdevtmpfsi|xmrig|kinsing'
# 4. 检查定时任务持久化
crontab -l
ls -la /etc/cron.d/ /var/spool/cron/
systemctl list-timers --all
# 5. 检查登录记录与系统日志
last -a
lastb -a
journalctl -u redis --since "3 days ago"
# 6. 检查 SSH 后门
cat ~/.ssh/authorized_keys
排查要点:redis-cli ping 若能无认证返回 PONG,暴露基本坐实;日志里出现 CONFIG SET、REPLICAOF、MODULE LOAD 等异常命令,几乎可以判定攻击路径。
四、修复步骤
1. 只绑定内网地址
bind 127.0.0.1 10.0.x.x
显式绑定本机与内网网卡,禁止监听公网接口,不要"就开一会儿"。
2. 设置强密码
requirepass <32位以上随机字符串>
通过配置管理或环境变量注入,不要硬编码进业务代码,并定期轮换;Redis 6 及以上建议配合 ACL,按业务划分只读/读写账号。
3. 禁用或重命名高危命令
rename-command CONFIG ""
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command SHUTDOWN ""
rename-command MODULE ""
重命名后需同步调整依赖这些命令的运维脚本与监控。
4. 使用专用低权限账号运行
不要让 Redis 以 root 运行:
useradd -r -s /sbin/nologin redis
chown -R redis:redis /var/lib/redis
再配合 systemd 单元的 User=redis、NoNewPrivileges=yes、ProtectSystem=strict 收紧权限,即便被利用也能压缩影响面。
5. 安全组只放行内网
6379 仅允许内网网段与指定应用服务器访问,公网一律拒绝,遵循最小放行原则。
清理阶段:删除被写入的 crontab 与 /etc/cron.d 下的可疑文件、清掉 authorized_keys 中的陌生公钥、终止恶意进程并删除对应文件。由于攻击者已具备 root 执行能力,必须假定凭据泄露——轮换全部 SSH 密钥、Redis 密码与业务密钥,必要时重装系统。
五、通用加固清单
- [ ]
bind限定为127.0.0.1与必要内网地址,绝不监听公网 - [ ] 保持
protected-mode yes - [ ] 设置高强度
requirepass,按需启用 ACL - [ ] 禁用/重命名
CONFIG、FLUSHALL、EVAL、MODULE等高危命令 - [ ] Redis 以专用非登录低权限账号运行,数据目录权限最小化
- [ ] 安全组/iptables 仅放行内网来源,公网端口关闭
- [ ] 限制
DEBUG、REPLICAOF等敏感能力 - [ ] 开启日志审计,监控
CONFIG、REPLICAOF、MODULE LOAD等异常命令 - [ ] 定期核查
authorized_keys、crontab、systemd timers - [ ] 建立配置基线巡检,避免"临时改一下就忘"
六、总结
Redis 未授权访问并非已经过时的问题,它反复出现在生产环境里,原因几乎总是同一组:监听地址没有收口、protected-mode 被关掉、密码没设或太弱。单看每一项都像是可以"先放一放"的小配置,叠加起来却等于把一台以 root 权限运行的服务直接交给公网。真正的教训是,安全防护要在部署那一刻就位,而不是等 CPU 被挖矿打满之后才补。把绑定、认证、命令禁用、运行账号、网络放行这五件事一次性做对,再辅以持续巡检,才能让这类事故停在配置检查阶段,而不是演变成一次应急。