| 版本 | 支持状态 |
|---|---|
| 4.x | ✅ 接受漏洞报告(与 INFO 的 concurrentcache_version 一致) |
| 未来版本 | ✅ 接受漏洞报告 |
如果你发现安全漏洞,请通过以下方式联系我们:
请不要在 GitHub Issues 或 Discussions 中公开报告漏洞。
我们会在 48 小时内回复漏洞报告。
- 我们承诺不对外公开报告者的信息
- ,我们会协调发布修复补丁
- 我们会根据情况决定是否公开披露漏洞
对干报告漏洞的研究者,我们会在得到允许后将其加入致谢名单。
当前实现没有任何认证:没有 requirepass、没有 ACL、没有 TLS,监听地址是 INADDR_ANY,FLUSHDB / DEBUG / SAVE / CLUSTER 对任何能连上端口的客户端开放。集群总线端口(port + 10000)同样无认证,而它会直接执行收到的复制命令。
因此本项目的安全前提是:只放在可信内网,或前面加带认证的网络代理。不要把它暴露到公网,也不要把它当作 Redis 的替代品直接 docker run -p 到宿主网上。
ci.yml 的必过门禁覆盖:Release 与 Debug(assert 生效)构建、gate 层测试、ASan 构建、Docker 构建,以及 scripts/ci/check_consistency.py 的八组一致性:命令注册表 vs 复制写白名单、test/ 文件是否真的进入构建、配置项是否真的被读取、端口三处是否一致,加上 README/api 命令表与 ctest 表、.md 相对链接可解析、INFO 版本号、镜像仓库地址是否真被推送、配置表双向核对、e2e 脚本清单,以及"只声明未定义且零调用的成员函数"。
覆盖不到的:daily.yml 的 sanitizer / 长压测 / e2e / 与真 Redis 的对照只报告不拦合并(它们不在 required check 里,红了要人去看,但不会挡住合并)。已知尚未修复的缺陷逐条登记在 ci/known-failures.txt——名单规则是每项都必须当前真的失败,所以它只能变短,少一行就是修好一个。
有两条,而且都不是"文档里写着以后再说"就完事的:
集群总线报文没有真实性认证。 端口 port + 10000 收到的报文不携带任何共享密钥衍生的签名,因此收到即被信任其内容。现有缓解是四道各自的门:帧长上界 256MB 且畸形帧直接断链(否则 length 会让接收端越界读最多 4GB)、控制面报文自称的来源地址与实际链路地址一致(两侧都写成字面地址时;任一侧是主机名则不比地址)、拒绝自称是本节点的报文、一帧从第一次残缺起超过"10 秒宽限 + 声明长度按 1MB/s 折算"即断链(200MB 的帧约 210 秒,滴包几十年都凑不完,一定超)。最后那道堵的是滴包:对端先送一个声明 256MB-1 的合法帧头,之后每 10 秒送 1 个字节——每个字节都会刷新空闲时间戳,所以只靠 last_recv_time_ 的超时永远不触发,而这条链路的接收缓冲一路涨到接近 256MB,总线链路数又不设上限。改成按"这一帧挂了多久"计时之后,攻击面还剩一个变体:每次只滴几个字节、永远凑不满 2120 字节的帧头,让记账每轮从零开始——缓冲确实涨不动,但链路和 fd 会无限挂着,所以"半截帧头"同样按一帧没着落记账(期限按头长折算,等于固定 10 秒)。前面那两道地址门在同机多节点的部署下(本仓库 e2e 与 daily 的拓扑就是全 127.0.0.1)分不出真假——那时任何本机进程都能伪装成任意一个集群成员,而 sender_name 正是故障判定与 failover 法定人数的计票单位。
身份对账拒掉的报文数量走 CLUSTER INFO 的 cluster_bus_identity_rejected:<n>。加这个数是因为被拒的报文里有复制数据:本仓库把被复制的写命令塞在同一条总线上,早期版本要求"声称的 ip 与链路地址逐字相等",于是 hostname / Docker service-name / NAT 之后对端自报的地址与链路地址永远不等,报文被静默丢掉、对端等不到 PONG 还会把本端标成 PFAIL。现在控制面只在两侧都是字面地址时比地址,数据面改成认"这个名字已在成员表里"(握手阶段不会发数据面报文,所以随机来客仍然被拒)。这两条都还是启发式,不是授权——真正的凭据是下面那个签名。
先纠正一句这里先前写错的话:Redis 并不给总线报文签名。 cluster_legacy.c 里没有任何 sign / hmac / authenticate 逻辑(那个 internal_secret 是模块间通信用的,不上总线),redis.conf 把话说得更直白:总线端口 "has no authentication of its own",而认证它的办法是 tls-cluster yes —— 开了之后对端必须拿出能用 CA 验通过的证书,两个方向都要,跟只管客户端端口的 tls-auth-clients 无关。所以这一项的正解是接 TLS(find_package(OpenSSL) + SSL_accept/SSL_connect 包住总线链路,验证过 CA 才读写帧头),不是补一个 Redis 没有的签名。
总线入站链路已有配额:kMaxInboundLinks = 512(cluster_bus.h:61),超限的连接在 accept 处直接关掉并计入 cluster_bus_inbound_refused。 每条链路 = 一个 fd + 最大 256MB 的接收缓冲,而建立链路只需要对端能连上 port + 10000。上面那四道门管的是"单条链路能不能挂太久",没有管"能挂多少条"。缺的是 ClusterConnection 里一个链路数上界(按成员表规模给值),超了就拒新建,并把当前链路数与拒绝数计进 CLUSTER INFO。这条不依赖任何外部组件,是这两条里代价最小的。
如果仍要在 TLS 之外另加一层报文签名,那是本项目自己的方案,不要写成"与 Redis 一致"——真 Redis 节点不会带这个签名,加了就直接断掉与 Redis 集群线的互通。形状是:所有节点相同的共享密钥对帧做 HMAC-SHA256,截断 16 字节附在帧尾,接收端在解析之前用常量时间比较验完再动状态;代价同样是引入 OpenSSL(libssl-dev / Dockerfile / vcpkg 清单都要改),而且必须把"没配密钥就拒绝总线"定成显式选项而不是静默放过。
历史上登记在同一处、如今已经修掉的:RESP 数组嵌套深度上限(resp.{h,cpp} 的 kMaxNestingDepth + DepthGuard)、客户端输入/输出缓冲上限与高水位断开(Connection 构造时读 client_query_buffer_limit / client_output_buffer_limit,超限即断链)、总线帧长上界、总线自称来源校验、总线半帧滴包。其中四条有会红的用例守着:缓冲高水位在 V3Tests(test/network_test/connection_lifetime_test.cpp),帧长边界、来源对账与滴包停留都在 ClusterTests(test/cluster_test/cluster_link_framing_test.cpp),都在 gate 层,改回去就挡住合并。嵌套深度那条目前没有专门用例,只有实现里的上限在起作用。