CVE-2026-71890 Bouncy Castle Java MLS 外部提交验证漏洞
影响攻击者可提交恶意外部提交,将任意成员从群组中移除
Bouncy Castle for Java 1.86 之前版本在验证 MLS(RFC 9420)外部提交的提案列表时存在缺陷。org.bouncycastle.mls.protocol.Group.validateExternalCachedProposals 仅按类型统计提案并限制被移除叶节点索引范围,但未验证被移除叶节点与加入者之间的关联。
影响范围
Bouncy Castle for Java 1.86 之前的版本。具体受影响版本范围暂无更详细的公开信息。
漏洞详情
根据 RFC 9420 第 12.2 节,外部提交中最多允许一个 Remove 提案,且要求提交路径字段中的 LeafNode 满足针对被移除叶节点的 Update 条件,特别是其凭据需包含被移除参与者可接受的标识符。该路径上未应用普通提案列表验证器的自移除规则,也未补充相应校验。任何持有群组公开 GroupInfo 的一方均可提交指定任意成员 LeafIndex 的 Remove 提案,并让所有成员执行,从而将该成员驱逐并接管其在棘轮树中的位置。
利用条件与风险
利用前提是攻击者持有群组的公开 GroupInfo,而该信息正是外部加入者本应获得的。实战中可导致任意成员被驱逐及槽位被接管,破坏群组完整性与成员控制权。
修复建议
官方修复方案为升级至 Bouncy Castle for Java 1.86 或更高版本。临时缓解措施暂无公开信息。
In Bouncy Castle for Java before 1.86, validation of an MLS (RFC 9420) external commit’s proposal list, org.bouncycastle.mls.protocol.Group.validateExternalCachedProposals, counted the proposals by type and bounded the removed leaf index but never established that the removed leaf had anything to do with the joiner. RFC 9420 sec. 12.2 permits at most one Remove proposal in an external commit, with which the joiner removes an old version of themselves, and requires that where one is present the LeafNode in the commit’s path field meet the criteria it would have to meet in an Update for the removed leaf, in particular that its credential present identifiers acceptable for the removed participant. The ordinary proposal-list validator’s self-remove rule is deliberately not applied on this path, because a resync commit legitimately removes a leaf the joiner owns, but nothing was put in its place. Any party holding the group’s public GroupInfo, which is precisely what an external joiner is meant to be given, could therefore commit a Remove naming any member’s LeafIndex and have every member apply it, evicting that member and taking over their slot in the ratchet tree. The credential check that should have prevented this existed only in the gRPC interop harness and so protected no other caller of the public Group.externalJoin and Group.handle API. An external commit carrying a Remove is now accepted only when the removed leaf’s credential is identical to the one in the joiner’s own new leaf, on both the sending and the receiving side.