【漏洞分析】Redis6.2–8.8认证后RCE漏洞深度分析与复现

admin 2026-08-04 07:59:56 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文剖析了影响Redis6.2至8.8的三组认证后RCE漏洞,利用Stream双重释放、TDigest堆溢出及TopK野指针释放构造任意读写原语,劫持hashFunction实现无破坏性代码执行。利用无需DEBUG模式且服务可恢复,但受TCP分片限制仅适用于SSRF或本地回环场景。建议通过ACL禁用RESTORE等高危命令实现根本防御。 综合评分: 95 文章分类: 漏洞分析,渗透测试,WEB安全,漏洞POC


cover_image

【漏洞分析】Redis 6.2–8.8 认证后 RCE 漏洞深度分析与复现

原创

博智非攻研究院 博智非攻研究院

博智非攻研究院

2026年7月28日 14:40 江苏

在小说阅读器读本章

去阅读

影响范围:Redis 6.2.22 / 7.4.9 / 8.6.4 / 8.8.0 / 8.8.1(覆盖 6.x – 8.x 全线版本)

漏洞类型:认证后堆利用 → 远程代码执行

利用前提:Redis AUTH凭据 + 本地回环投递(SSRF / 容器提权)

核心特点:无破坏性利用,执行后服务恢复正常;无需DEBUG 模式

2026 年 7 月,安全研究团队 berabuddies 公开了一系列影响 Redis 6.2 至 8.8 全线版本的认证后远程代码执行漏洞。三组漏洞分别利用 Stream 消费者组共享 NACK 双重释放RedisBloom TDigest 堆溢出、以及 TopK 野指针释放,在 jemalloc 堆上构建任意内存读写原语,最终劫持控制流执行系统命令。所有利用方式均为无破坏性——服务器在代码执行后仍可恢复正常运行。

本文包含完整的漏洞原理分析、利用链详解与实际 RCE 复现输出。

一、漏洞概述

| 序号 | 漏洞类型 | 影响版本 | CVE 关联 | 补丁状态 | | — | — | — | — | — | | 1 | Stream NACK Double Free | 6.2.22 / 7.4.9 / 8.6.4 | CVE-2026-25243 补丁绕过 | 8.8.0(PR #15081)修复 | | 2 | TDigest 堆溢出 | 8.8.0 | 新发现(无 CVE) | 未修复 | | 3 | TopK 野指针释放 | 8.8.0 / 8.8.1 | CVE-2026-25589 补丁绕过 | 未修复 |

利用前置条件:

1.需要有效的 Redis 认证凭据(AUTH命令)

2.目标需开放 EVALRESTOREXGROUP等命令

3.漏洞 2/3 还需 RedisBloom 模块(Redis 8.x 默认捆绑

4.对 jemalloc 堆布局敏感,推荐在空实例上利用

二、漏洞原理分析

2.1 漏洞1:Stream 消费者组共享 NACK 双重释放

Redis Stream 的 RDB 加载逻辑(rdb.c)在处理消费者组的 PEL(Pending Entries List)时存在缺陷。当一条消息被两个消费者引用,但全局 PEL 大小仅为 1 时,两个消费者的 NACK 会指向同一个 streamNACK结构体(24 字节,jemalloc tcache bin24)。

// 攻击流程 — 触发 Double Free
RESTORE <crafted_stream> &nbsp; &nbsp; &nbsp;&nbsp;// 精心构造的 Stream:alice 和 bobxx 两个消费者
// 全局 PEL=1 → 两个消费者共享同一个 streamNACK

XGROUP DELCONSUMER alice &nbsp; &nbsp; &nbsp; &nbsp;// 第一次释放 streamNACK
XGROUP DELCONSUMER bobxx &nbsp; &nbsp; &nbsp; &nbsp;// 第二次释放同一块内存 → Double Free!

为什么是补丁绕过:CVE-2026-25243 最初的修复尝试未能完全消除多消费者共享同一 NACK 的场景。本文中的 RDB payload 构造方式成功绕过了该修复,直到 Redis 8.8.0(PR #15081)才从根本上修复。影响 Redis 6.2.22、7.4.9、8.6.4

2.2 漏洞2:TDigest 堆溢出(RedisBloom 模块)

Redis 8.8.0 捆绑的 RedisBloom 模块中,TDigestRdbLoad函数在加载 TDigest 数据结构时存在线性堆溢出。攻击者通过 RESTORE传入精心构造的 payload:声明 compression=1(Redis 据此仅分配 16 条目空间),但 cap字段被设置为远大于实际分配量的值。写入循环受 cap控制,数据线性溢出覆写相邻堆块中 victim tdigest 结构体的关键字段。

| 字段 | 正常值 | 溢出后 | 作用 | | — | — | — | — | | nodes_mean | 原始地址 | X(攻击者指定) | 指向读目标地址 | | nodes_weight | 原始地址 | Y(攻击者指定) | 指向写目标地址 | | merged_nodes | 原始值 | k | 控制读取 qword 数量 | | cap | 原始值 | 0x40000000 | 绕过边界检查 |

溢出后的 victim struct 被劫持为”Oracle“——正常的 DUMP命令从 nodes_mean指向的地址读取数据(任意读),TDIGEST.ADD命令向其写入数据(任意写)。截至 PoC 发布时,该漏洞尚未修复

2.3 漏洞3:TopK 野指针释放(RedisBloom 模块)

RedisBloom 模块的 TopKRdbLoad函数对 CVE-2026-25589 的修复不完整。错误处理路径中仅将 floor(heapSize/24)个 item 指针清零,但延迟清理函数 TopK_Destroy释放全部 k个指针。

// CVE-2026-25589 补丁绕过 — TopK_Destroy 越界读取
heapSize =&nbsp;48, k =&nbsp;3
// 补丁只清零了 floor(48/24) = 2 个 item 指针
// 但 TopK_Destroy 释放全部 k=3 个 heap[i].item

// heap+56 读取到相邻 slab 的 robj.ptr
// → 活 sds 被 je_free() 释放 → 野指针释放(Use-After-Free)

当相邻 slab 恰好是攻击者喷射的 kvobj(RAW 字符串键),偏移 8 处的 robj.ptr被意外释放,但 kvobj 仍然存活——造成经典的 Use-After-Free。RedisBloom v8.8.0 和 v8.8.2 均受影响截至发布时未修复

三、利用链分析

3.1 通用利用模式

三组漏洞的利用链遵循相同的通用模式:

堆漏洞触发 → 堆喷占位 → 篡改内存结构 → 构造任意读写原语
&nbsp; &nbsp; → 信息泄漏(PIE + libc)→ 控制流劫持 → 代码执行 → 环境恢复

关键信息泄漏路径(三组漏洞通用):

1. PIE 基址:Lua string.format的 CClosure 对象 → 读取 str_format函数地址 → 计算 PIE 基址

2. libc 基址:读取 write@GOT→ 得到 libc 基址 → 计算 system地址

3. 控制流目标:读取 server.db→ 遍历数据库数组 → 找到空 db 的 dict 指针

3.2 漏洞1 利用链详解(以 Redis 6.2.22 为例)

阶段一:G2 单释放 UAF → 任意读(PIE 泄漏)

  1. RESTORE

    精心构造的 Stream → 两个消费者(alice / bobxx)共享一个 streamNACK(24B)

  2. XGROUP DELCONSUMER alice

    → 释放该 NACK(24B 进入 tcache bin24)

  3. 喷射 22 字节键名的 SET命令 → 键名 sds(type5,1+22+1=24B)抢占释放的 NACK 内存

  4. 键名内容嵌入伪造的 streamConsumer指针→ NACK 的 consumer指针被劫持

  5. XPENDING

    命令回显 nack->consumer->name→ 读取伪造 consumer 中指定地址的内容 → 任意内存读取

  6. 通过 Lua string.format的 CClosure 泄漏 PIE 基址,通过 write@GOT泄漏 libc 基址

阶段二:Double Free → Fake DictEntry → 任意读写

  1. 重复 double free(RESTORE→ DELCONSUMER×2)→ 释放 stream 的 dictEntry(同样 24B)

  2. 22B 键名喷射抢占释放的 dictEntry → 键名内容嵌入 fake key / value / next 指针

  3. 伪造的 robj(type=RAW, refcount=1)挂在 BUFkey 的 sds 内部

  4. GETRANGE k001

    → 读取 fake robj 指向的任意地址(任意读

  5. SETRANGE k001

    → 写入 fake robj 指向的任意地址(任意写

阶段三:Endgame → RCE

  1. 泄漏 db[0].dict指针,验证 dictType == dbDictType

  2. 扫描空数据库 db[N](通过 D + k*96偏移遍历,测试 dictType匹配)

  3. 在 BUF sds 内构造 fake dictTypehashFunction = system(libc 地址),keyCompare = dictSdsKeyCompare(合法值)

  4. SETRANGE

    将 db[N].dict->type覆写为 fake dictType 地址(7 字节写入)

  5. SET dummykey

    GET <shell_command>→ system(key)→ RCE

  6. 恢复 dictType原值,删除临时 key,恢复 saveslowlog配置

3.3 漏洞2 利用链详解(TDigest 堆溢出)

阶段一:堆喷与溢出

  1. 创建 2000个 TDIGEST.CREATE实例(compression=1 → 每个 80B struct + 2×128B 数组)

  2. jemalloc 将后续 victim 紧凑排列:128B 数组 run 紧邻 80B struct run 下方

  3. DEL v794

    → 其 struct(80B)和 arrays(2×128B)进入 tcache 顶部(class-80 和 class-128 bin)

  4. RESTORE

    crafted payload → jemalloc 复用刚释放的块 → 数据线性溢出覆盖上方相邻 victim struct

  5. 被覆写字段:nodes_mean=Xnodes_weight=Ymerged_nodes=kcap=0x40000000

Oracle 的含义

这里借用了密码学中的”Oracle”概念——攻击者造出一个可以被反复”查询”的通道。正常 Redis 命令(DUMPTDIGEST.ADD)本意是导出/更新 tdigest 数据,但攻击者通过堆溢出破坏了受害者 struct 的内部指针字段,使得这些命令的行为被劫持,变成了读/写任意内存地址的工具

阶段二:构造任意内存读写通道(Oracle)

  1. TDIGEST.INFO

    遍历所有 victim → 找到 cap == 0x40000000的受害者 key

  2. 任意读

    DUMP victim→ TDigestRdbSave从 nodes_mean(即 X)地址开始序列化 k个 qword 作为 RDB 数据返回

  3. 任意写

    TDIGEST.ADD victim <value>→ 将攻击者传入的 double 值写入 nodes_mean(即 X)地址,同时将常量 1 写入 nodes_weight(即 Y)地址

  4. 每次读/写后,DEL中间 key → 重新执行 RESTORE+ 堆溢出 → 用新的 X / Y / k 参数重建通道,实现反复多次的任意地址读写

阶段三:信息泄漏与 RCE

  1. 读取 CClosure+32→ str_format→ PIE 基址

  2. 读取 write@GOT→ libc 基址 → system地址

  3. 读取 server.db→ db 数组 → db[1].keyskvstore

  4. 验证 kvstore.dtype.hashFunction == dictSdsHash

  5. 覆写 hashFunction为 jmp *0x78(%rdi)gadget(hdr_iter_next

  6. GET "<cmd> #<pad><system_addr>"

    → dictFind→ gadget → system(cmd)

  7. 恢复 hashFunction,删除临时 key

3.4 漏洞3 利用链详解(TopK 野指针释放)

阶段一:野指针释放与 UAF

  1. 喷射 800个 kvobj:22 字符键名 + 245 字符 value(value sds 为 jemalloc class-256

  2. RESTORE

    crafted TopK payload(heapSize=48, k=3, heap="H"*48

  3. RESTORE 失败 → 错误路径 → TopK_Destroy读取 heap+56(超出实际堆大小,进入相邻 slab)

  4. heap+56

    处恰好是某个喷射 kvobj 的 robj.ptr(偏移 8 字节处)

  5. je_free(robj.ptr)

    → 活 value sds 被释放 → kvobj 仍存活 → Use-After-Free

阶段二:UAF 占位 → 内存别名(Aliasing)→ 任意读写

内存别名(Memory Aliasing)核心机制

TopK_Destroy释放了受害 key 的 value sds(class-256 堆块,地址记为 V)。攻击者执行 SET k2(220 字符键名 → kvobj 本身分配在 class-256)→ jemalloc 将 k2 的 kvobj 分配到 V。此时同一块物理内存 V被两个 key 以不同视角看待:

受害 key:认为 V 是自己的 value 字符串内容

k2:其 kvobj 元数据(robj结构体,含 type / encoding / refcount / ptr 字段)也在 V 上

  1. GETRANGE victim 0 23

    → 读取 V 处 24 字节 → 偏移 16(K2_PTR_OFF)恰好是 k2 的 robj.ptr→ 泄漏 k2 value sds 地址(记为 S2)

  2. SETRANGE victim 16 <8字节>

    → 改写 V+16 处的 robj.ptr→ 将 k2 的 value 指针重定向到攻击者指定的任意地址 T

  3. 此后 GETRANGE k2 0 N→ 从地址 T 读取 N 字节(任意读

  4. SETRANGE k2 0 <data>

    → 向地址 T 写入数据(任意写

通道能工作的原因:k2 的 value 是 RAW 编码且 refcount=1,GETRANGESETRANGE直接操作 robj.ptr指向的 sds 缓冲区,没有额外的编解码跳转。

阶段三:信息泄漏与 RCE

  1. Lua string.formatCClosure → str_format→ PIE 基址

  2. write@GOT

    → libc 基址 → system

  3. server.db

    → db 数组

  4. 在 k2 的 value sds(S2)中构造 fake dictTypehashFunction = jmp *0x78(%rdi)gadget

  5. 找到可写的 db dict → 覆写 dict->type为 S2

  6. GET "<cmd> #<pad><system_addr>"

    → gadget → system(cmd)

  7. 恢复 dict->type,删除临时 key

3.5 终局:hashFunction 劫持 → system()

三组漏洞的终局(Endgame)都采用相同的控制流劫持技术——覆写数据库 dict 的 hashFunction指针

控制流劫持流程

  1. 通过任意读找到一个空数据库db[N],验证其 dictType

  2. 构造 fake dictType,将 hashFunction覆写为 system(或 JMP gadget)

  3. SET dummykey

    → 使 dict.used > 0(避免 dictFind因 used==0短路)

  4. GET "<shell_command>"

    → dictFind调用 hashFunction(key)→ 实际执行 system("<shell_command>")

  5. 执行完毕后恢复dictType原值,删除临时 key → 服务器恢复正常

漏洞 2 和漏洞 3 使用了额外的 JMP gadget(hdr_iter_next中的 jmp *0x78(%rdi)),在 key 字符串偏移 0x78 处嵌入 system地址实现间接跳转。key 格式为 "<cmd> #<padding><system_addr>"#后内容被 shell 当作注释忽略,system实际只执行 #前的命令部分。

3.6 各漏洞的读写原语差异

| 漏洞 | 堆原语 | 读写构造方式 | | — | — | — | | 漏洞1(Stream) | Double Free | 喷射 22B 键名抢占 → 伪造 dictEntry → GETRANGE/SETRANGE | | 漏洞2(TDigest) | 线性堆溢出 | 覆写 victim struct 指针 → DUMP/TDIGEST.ADD | | 漏洞3(TopK) | 野指针释放 → UAF | 内存别名占位 → 劫持 robj.ptr→ GETRANGE/SETRANGE |

四、RCE 复现

4.1 环境

Redis 8.8.0Docker(含 RedisBloom 模块,默认捆绑)

利用脚本:T88_exploit.py(TopK 野指针释放 → RCE,CVE-2026-25589 补丁绕过)

4.2 编译 CRC64 签名库

利用脚本需要 CRC64 签名库来生成合法的 RDB payload 校验和:

4.3 执行利用

4.4 RCE 验证

五、实战考量

5.1 TCP 分片:最大的实战限制

本系列堆漏洞对 jemalloc 堆布局极端敏感。跨网络利用时,受限于网卡 MTU(通常 1500 字节),巨大的 Payload 会被 TCP 分片。这导致 Redis 在接收数据时频繁触发 querybuf动态扩容与内存释放,彻底破坏漏洞利用所需的精确堆布局,引发段错误(SIGSEGV)导致目标崩溃。

5.2 利用场景评估

| 场景 | 可行性 | 说明 | | — | — | — | | SSRF 攻击链 | 极高 | Web 应用 SSRF → gopher/dict 协议本地投递 — 最核心场景 | | 本地提权 / 容器逃逸 | 高 | 已获低权限 Shell,通过回环地址提权至 Redis 运行权限 | | K8s Sidecar 提权 | 高 | 同 Pod 本地容器间通信走 loopback | | 内网横向移动 | 极低 | TCP 分片导致堆布局不可控,目标大概率崩溃 | | 公网暴露 Redis | 极低 | 网络抖动使堆布局完全不可控 |

5.3 优势与局限

优势

  • 覆盖面广

    :三组漏洞覆盖 6.2.x – 8.8.x 全线主流版本

  • 无破坏性

    :利用后服务器不崩溃,执行后自动恢复环境

  • 无需 DEBUG

    :7.4/8.x 的 PoC 完全脱离 DEBUG 命令

  • 补丁绕过能力强

    :漏洞1 和漏洞3 均为已公开 CVE 补丁的二次绕过

  • 利用稳定

    :空实例成功率接近 100%(6.2: 10/10, 7.4: 5/5, 8.6: 25/25)

局限

  • 网络敏感

    :只能通过回环地址利用,跨网络几乎 100% 导致崩溃

  • 需要凭据

    :不具备未授权 RCE 能力,需先获取 AUTH 密码

  • 并发干扰

    :生产环境中其他客户端的并发操作会破坏堆布局

  • 堆残留

    :利用后堆中残留假结构体和悬空指针,不建议执行 FLUSHALL或 SAVE

  • 版本依赖

    :强依赖特定 libc/jemalloc 版本偏移,非官方构建需事先校准

六、修复建议

首要措施:ACL 限制高危命令

# redis.conf — 禁用利用链依赖的关键命令
rename-command RESTORE ""
rename-command DEBUG ""

# 或使用 ACL(Redis 6.0+)按用户精细控制
ACL SETUSER app -RESTORE -DEBUG -XGROUP on >password ~*

三组漏洞均依赖 RESTORE命令注入恶意 RDB payload。禁用该命令可从根本上阻断攻击。

| 措施 | 效果 | | — | — | | ACL 禁用 RESTORE / DEBUG / XGROUP | 阻断 payload 注入 → 根治 | | 升级 Redis 版本 | 8.8.0 修复漏洞1,但漏洞2/3 截至发布仍未修复 | | 使用强密码 + 定期轮换 | 提高认证门槛,减少凭据泄露风险 | | 网络隔离(禁止外部直连) | 配合 SSRF 防护 → 阻断最主要攻击入口 | | SSRF 防护(阻断 gopher/dict 协议) | 阻止 Web 层通过 SSRF 投递 Payload | | 监控异常命令模式 | 大量 RESTORE + XGROUP DELCONSUMER 组合为高危信号 |

参考

  • berabuddies/redis-poc(PoC 及利用脚本)
  • CVE-2026-25243(Stream consumer-group shared-NACK double free)
  • CVE-2026-25589(TopK wild free)
  • Redis PR #15081(漏洞1 修复,合并至 8.8.0)


免责声明:

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

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

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

本文转载自:博智非攻研究院 博智非攻研究院 博智非攻研究院《【漏洞分析】Redis 6.2–8.8 认证后 RCE 漏洞深度分析与复现》

评论:0   参与:  0