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 命令常留在此处