RedisCluster集群取证

当我们成功仿真出 Redis 服务器的时候,往往需要进行网络配置并拉起各实例,才能恢复出完整的 Redis Cluster 集群架构

Redis Cluster 基础知识

什么是 Redis Cluster

Redis Cluster 是 Redis 官方的分布式方案(3.0 版本引入)。整个集群的 key 空间被划分为 16384 个哈希槽(slot),通过 CRC16(key) mod 16384 决定 key 落在哪个槽;每个槽由一个主节点(Master)负责,主节点可以有若干从节点(Replica/Slave)做数据冗余。节点之间通过 Gossip 协议 在集群总线端口(数据端口 + 10000,如 6379 对应 16379)交换心跳与槽位信息,不依赖中心节点。当主节点失联超过 cluster-node-timeout(默认 15000ms)时,其从节点发起选举执行 failover 接管槽位。

取证要点速览

类别关键位置
数据端口 / 集群总线端口6379 / 16379(bus port = port + 10000)
主配置文件/etc/redis/redis.conf(Debian/Ubuntu)、/etc/redis.conf(RHEL/CentOS),或自定义路径
集群拓扑文件nodes.conf(cluster-config-file 指定,默认在工作目录下)
工作目录dir 配置项指定,默认 /var/lib/redis
RDB 快照<dir>/dump.rdb(dbfilename 指定文件名)
AOF 目录(Redis 7+)<dir>/appendonlydir/(内含 manifest 与 aof 文件)
AOF 文件(Redis ≤6)<dir>/appendonly.aof
ACL 用户文件aclfile 指定,常见 /etc/redis/users.acl
日志文件logfile 指定,常见 /var/log/redis/redis-server.log
运行参数(在线)redis-cli -p <port> CONFIG GET *

离线取证技巧:不知道实例怎么启动的怎么办

仿真机中 Redis 可能没有跑起来,先从配置文件和持久化文件入手还原现场:

# 找到全部 redis 配置文件
find / -name "redis*.conf" 2>/dev/null
# 配置文件里确认关键项
grep -E "^(port|bind|dir|dbfilename|appendonly|aclfile|logfile|cluster-enabled|cluster-config-file|requirepass|masterauth)" /etc/redis/redis.conf
# 离线校验持久化文件完整性
redis-check-rdb /var/lib/redis/dump.rdb
redis-check-aof --fix /var/lib/redis/appendonlydir/appendonly.aof.1.incr.aof   # 可修复截断的 AOF

仿真环境网络配置

启动网卡

仿真出的服务器默认网卡可能未启用,需手动拉起并获取 IP:

ip link set ens33 up
dhclient ens33

拉起集群实例

集群每个节点是独立的 redis-server 进程,逐台/逐实例启动:

redis-server /etc/redis/redis.conf          # 按各自节点的配置文件启动
# 或 systemd 方式
systemctl start redis-server
ss -lntup | grep redis                      # 确认 6379 与 16379 均已监听

仿真 IP 变化导致集群失联

nodes.conf 中记录的是案发时的节点 IP,仿真后 IP 变了会导致节点互相不认识,这是集群取证仿真最常见的问题:

# 方法一:用 cluster-announce-ip 让节点对外宣告仿真 IP(写进各节点 redis.conf)
cluster-announce-ip 192.168.170.51
cluster-announce-port 6379
cluster-announce-bus-port 16379

# 方法二:启动后在任一节点上 meet 所有节点的新 IP,Gossip 会自动扩散拓扑
redis-cli -p 6379 cluster meet 192.168.170.52 6379
redis-cli -p 6379 cluster meet 192.168.170.53 6379
redis-cli -c -p 6379 cluster nodes    # -c 启用集群模式自动重定向

若开启了 requirepass,所有 redis-cli 命令需加 -a <密码>(或先 AUTH),从节点同步还需配置中的 masterauth。

时钟偏差

有时候仿真出来的几台节点时间有偏差,会影响 failover 判定(cluster-node-timeout 计时)、副本数据时效性(master-repl-offset 与复制积压缓冲区)以及日志时间线对齐,需要同步时间

#1. 关闭自动同步(防止 chrony/NTP 与手动设置打架)
timedatectl set-ntp false
systemctl stop chrony && systemctl disable chrony

#2.手动统一时间(在每台节点上执行,时间保持一致)
timedatectl set-timezone Asia/Shanghai
timedatectl set-time "2024-01-01 12:00:00"
hwclock --systohc        # 同步写入硬件时钟 RTC

#3.确认时间一致(逐台执行比对)
date
timedatectl

#4.重启实例(时间一致后重启恢复集群心跳与选举)
systemctl restart redis-server

注意:Redis 自身依赖系统时钟计算 cluster-node-timeout 与 key 过期时间,时间回拨可能导致带 TTL 的 key 提前过期,取证导出前先做快照备份。

系统取证

基本系统信息

Redis 版本

redis-server --version            # 服务器版本
redis-cli -p 6379 INFO server     # 在线查看:redis_version、redis_mode(cluster)、run_id、进程号、配置文件路径
cat /etc/os-release               # 底层系统版本

INFO server 中的 config_file、executable、run_id 字段可直接定位配置与进程,是确定取证路径的第一步。

主机内核版本

uname -a
uname -r

主机名与节点列表

hostnamectl
cat /etc/hostname
cat /etc/hosts         # 集群各节点的 IP/主机名映射常记录在此

时间服务器地址

cat /etc/chrony/chrony.conf    # server/pool 行即时间源
chronyc sources                # 在线状态查看
timedatectl                    # 时区与时间同步状态

时钟偏差

redis-cli -p 6379 TIME           # Redis 视角的服务器时间(秒+微秒)
# 逐节点执行比对,结合 date 输出判断各节点时钟差

集群信息

集群状态

redis-cli -c -p 6379 CLUSTER INFO

关键字段:

  • cluster_state:ok/fail —— 集群是否可用(fail 通常因槽位未全覆盖或 cluster-require-full-coverage yes 下有节点失联)
  • cluster_slots_assigned:16384 —— 已分配槽位数,不等于 16384 说明集群不完整
  • cluster_known_nodes、cluster_size —— 节点数与主节点数
  • cluster_current_epoch —— 集群配置纪元,每次 failover/槽位迁移自增,可用于判断集群拓扑变更次数

节点拓扑

redis-cli -c -p 6379 CLUSTER NODES

每行一个节点,字段依次为:节点ID IP:数据端口@总线端口 角色(master/slave/myself) 主节点ID ping_sent pong_recv 配置纪元 连接状态 槽位范围,例如:

a1b2... 192.168.170.51:6379@16379 myself,master - 0 1718000000000 1 connected 0-5460
c3d4... 192.168.170.52:6379@16379 master - 0 1718000001000 2 connected 5461-10922
e5f6... 192.168.170.54:6379@16379 slave a1b2... 0 1718000002000 1 connected
  • 槽位范围 0-5460 表明该主节点负责的槽段;状态为 fail/handshake/noaddr 的节点是重点排查对象
  • 带 [slot<-importing] / [slot->migrating] 标记说明案发时正在做槽位迁移(reshard),是重要的时间线线索

槽位分布

redis-cli -c -p 6379 CLUSTER SLOTS     # 槽段 → 主/从节点 IP:端口 的完整映射
redis-cli -c -p 6379 CLUSTER COUNTKEYSINSLOT <slot>   # 某槽内 key 数量
redis-cli -c -p 6379 CLUSTER GETKEYSINSLOT <slot> 10  # 抽样查看槽内 key

集群配置文件 nodes.conf

nodes.conf 由 Redis 自动维护(切勿手改后启动,校验失败节点会拒绝加载),内容与 CLUSTER NODES 输出一致,但离线可读:

cat /var/lib/redis/nodes.conf

取证价值:

  • 第一列 40 位十六进制节点 ID 是集群内节点的唯一身份,日志、复制偏移量追踪均以此标识
  • 文件末尾的 vars 行(currentEpoch、lastVoteEpoch)记录了选举历史
  • 结合文件 mtime 可推断最后一次拓扑变更(failover/增删节点)的时间

主从复制状态

redis-cli -p 6379 INFO replication
# master: 看 connected_slaves、slave0:ip=...,port=...,offset=...
# slave : 看 master_host、master_port、master_link_status:up/down、master_last_io_seconds_ago

master_link_status:down 或 master_last_io_seconds_ago 很大,说明案发前后复制链路已中断,数据可能不完整。

数据取证

在线查看数据

集群模式下数据分散在各节点,需逐节点导出;KEYS * 只返回当前节点数据且会阻塞,用 SCAN 代替:

redis-cli -c -p 6379 DBSIZE                          # 当前节点 key 总数
redis-cli -p 6379 --scan | head -100                 # 分批列出 key(不阻塞)
redis-cli -p 6379 --scan --pattern 'user:*'          # 按模式匹配
redis-cli -p 6379 TYPE <key>                         # 确认类型后用 GET/HGETALL/LRANGE/SMEMBERS/ZRANGE 读取
redis-cli -p 6379 MEMORY USAGE <key>                 # key 占用内存(4.0+)
redis-cli -p 6379 OBJECT ENCODING <key>              # 内部编码,辅助判断数据形态

导出方法一:—rdb 全量快照(推荐)

在线生成并拉取某节点的完整 RDB,等于把该节点数据打包带走:

redis-cli -p 6379 --rdb /导出目录/node1-dump.rdb
# 带密码:redis-cli -a <密码> -p 6379 --rdb /导出目录/node1-dump.rdb
# 每个主节点各导出一次,合并即为全集群数据

导出的 RDB 在取证机上可直接解析(见”存储取证”)或起一个临时 Redis 加载查询。注意 --rdb 走的是 SYNC 复制协议,得到的是导出时刻的一致性快照。

导出方法二:DUMP / RESTORE 逐 key 迁移

针对特定 key 做精确提取与恢复:

redis-cli -p 6379 --no-raw DUMP <key>                # 导出 RDB 序列化格式(含 TTL)
# 在目标实例恢复(0 为不设 TTL):
redis-cli -p 6380 RESTORE <key> 0 "<序列化值>"

适合只取少量关键 key(如疑似 flag、会话、凭据)而不搬空整个库的场景。

导出方法三:redis-dump 导出为 JSON/文本

需要人可读、可全文检索的产物时用第三方工具 redis-dump(Ruby):

gem install redis-dump
redis-dump -u redis://:<密码>@127.0.0.1:6379 > /导出目录/node1.json
# 回灌:redis-load < /导出目录/node1.json

JSON 产物可直接 grep 检索敏感字段,但丢失精确 TTL 与内存编码信息,建议与 RDB 快照并行保留。

导出后的传出与校验

# 传出(视网络情况选择)
scp /导出目录/* user@<取证机>:/path/

# 完整性校验(取证流程必须)
sha256sum /导出目录/node1-dump.rdb | tee node1-dump.sha256

存储取证

RDB 快照文件 dump.rdb

默认位于 dir 目录下(/var/lib/redis/dump.rdb)。RDB 是二进制快照,文件头 REDIS0009 等标识版本,尾部带 CRC64 校验(5.0+)。

redis-check-rdb /var/lib/redis/dump.rdb     # 校验完整性

离线解析用 redis-rdb-tools:

pip install rdbtools python-lzf
rdb --command json /var/lib/redis/dump.rdb > dump.json          # 转 JSON 全文检索
rdb --command memory /var/lib/redis/dump.rdb > memory.csv       # 每个 key 的内存占用
redis-keyspace-analyzer 或 rdb --key 'user:*' --command json dump.rdb   # 按模式筛选

也可在取证机上起临时实例加载:redis-server --dbfilename dump.rdb --dir /取证副本目录 --port 7777,然后用 redis-cli -p 7777 正常查询,务必操作副本,保留原文件与哈希。

AOF 持久化文件

开启 appendonly yes 时,每条写命令追加到 AOF,是比 RDB 更接近案发时刻的数据来源(含被删除前的写历史)。

  • Redis 7.0+(MP-AOF):目录 <dir>/appendonlydir/
    • appendonly.aof.manifest —— 清单,记录 base/incr 文件序列
    • *.base.aof / *.base.rdb —— 重写后的基线(RDB 或 AOF 格式)
    • *.incr.aof —— 增量命令流,可直接阅读,能看到明文写命令历史
  • Redis ≤ 6.x:单文件 <dir>/appendonly.aof
redis-check-aof /var/lib/redis/appendonlydir/appendonly.aof.1.incr.aof
# incr.aof 是 RESP 协议文本,可直接提取写操作时间线:
strings appendonly.aof.1.incr.aof | less

AOF 增量文件是 Redis 取证中价值最高的 artifact 之一:攻击者的 SET/DEL/CONFIG SET/SLAVEOF 等操作均按顺序记录在案。

配置中的持久化参数

grep -E "^(save|appendonly|appendfsync|dbfilename|dir)" redis.conf
redis-cli -p 6379 INFO persistence    # rdb_last_save_time、aof_last_rewrite_time 等,辅助重建时间线

rdb_last_save_time(Unix 时间戳)可判断最后一次快照时间,与 dump.rdb 的 mtime 互相印证。

用户与权限取证

requirepass 与 masterauth

grep -E "^(requirepass|masterauth)" /etc/redis/redis.conf   # 明文密码直接可见

requirepass 是客户端认证密码,masterauth 是从节点连主节点的密码,均为明文存储,提取后可用于在线访问,也是横向移动(复用口令)的素材。

ACL 用户(Redis 6.0+)

redis-cli -p 6379 ACL LIST                 # 全部用户及规则
redis-cli -p 6379 ACL GETUSER <username>   # 单个用户:密码哈希、key 权限、命令权限
redis-cli -p 6379 ACL WHOAMI               # 当前连接身份

规则形如 user zhangsan on #5d41402abc4b2a76b9719d911017c592 ~cached:* +get -@dangerous:

  • on/off —— 启用状态
  • #<sha256> —— 密码的 SHA256 哈希(不是原密码加盐,相同密码哈希相同,可彩虹表比对;!<pass> 前缀为明文)
  • ~pattern / %R~ %W~ —— key 访问范围
  • +cmd / -cmd / +@category —— 命令授权;-@dangerous 常见于限制 FLUSHALL/CONFIG/SHUTDOWN

ACL 持久化文件

aclfile 指定 ACL 落盘位置(ACL SAVE 写入),常见 /etc/redis/users.acl:

cat /etc/redis/users.acl    # 离线直接读取全部 ACL 规则

注意 ACL SETUSER 只改内存,是否落盘取决于是否执行过 ACL SAVE——对比文件与 ACL LIST 输出可发现案发后新增但未保存的账号。

日志取证

服务日志(重点)

cat /var/log/redis/redis-server.log       # logfile 指定的主日志
journalctl -u redis-server -e --no-pager

关注事件:

  • Connection with master lost / Connecting to MASTER —— 复制关系变化
  • Failover election won / Currently unable to failover —— failover 时间线
  • Manual failover requested by replica —— 人为触发 CLUSTER FAILOVER 的痕迹
  • Possible SECURITY ATTACK detected —— 危险命令(如恶意改 dir+dbfilename 写 SSH key)告警
  • Clear handshake / Address updated —— 集群成员变动

慢查询日志 SLOWLOG

SLOWLOG 只存在内存中,进程重启即消失,仿真恢复后第一时间提取:

redis-cli -p 6379 SLOWLOG GET 128          # 拉取全部慢日志(默认保留 128 条)
redis-cli -p 6379 SLOWLOG LEN
redis-cli -p 6379 CONFIG GET slowlog-log-slower-than   # 阈值(微秒,默认 10000)
redis-cli -p 6379 CONFIG GET slowlog-max-len

每条记录含 ID、Unix 时间戳、耗时微秒、完整命令及参数、客户端地址,是还原攻击者执行了哪些大 key 操作、KEYS * 扫描、批量 DEL 的直接证据。

命令实时监控 MONITOR(应急临时用)

在线实例上 MONITOR 可实时打印所有命令,但性能损耗大,仅用于短时间抓捕现行行为;正式取证优先依赖 AOF + SLOWLOG + 服务日志。

系统日志

cat /var/log/auth.log        # SSH/PAM 登录记录(攻击者如何登上 Redis 服务器)
cat /var/log/syslog
last -F                      # 登录历史
history / cat ~/.bash_history    # 攻击者执行过的 redis-cli 命令常留在此处