应对 Bech32 的安全漏洞的方案探讨

CKB 采用了 Bech32 地址格式 。Bech32 的一个安全漏洞 会给 CKB 引入安全隐患,使攻击者可以很容易地通过篡改合法地址构造出可以通过校验的非法地址或非预期的另一个合法地址。本文首先会介绍这种漏洞及可能造成的不利影响,并提出一个应对方案供大家探讨,也欢迎大家提出其他的解决方案。

Bech32 的安全漏洞

Bech32 地址格式存在一种特殊的情况,如果地址是以 p 结尾,则可以在倒数第二位插入或删除任意数量的 q,而不影响校验的正确性。比特币开发者 Pieter Wuille 从理论上分析了对 Bech32 地址进行字符替换、交换、插入等攻击的成功概率 ,结论是只有这一种特殊情况能被攻击者有效利用。

对 CKB 的不利影响

CKB 有两种地址,短地址和长地址。通过使用 Bech32 算法对地址的 payload 进行编码后生成最终的地址。

短地址:

payload = format_type | code_hash_index | args

长地址:

payload = format_type | code_hash | args

format_type 包含三种格式:

  • 0x01,短地址格式
  • 0x02,Data 类型的长地址
  • 0x04,Type 类型的长地址

payload 中的 format_typecode_hash_indexcode_hash 都是固定长度的字符串。因此,若攻击者利用漏洞在 Bech32 地址的倒数第二位插入或删除 q 字符,只会影响解析出来的 args 内容:args 会增加或减少一些字符。一个合法的地址被篡改后,会因 args 的改动变为一个非法地址或者另一个非预期的合法地址,而这些地址却可以通过 Bech32 的 checksum 校验。

我们可以构想这样几个攻击场景:

  • 某网站提供了一个公开的 CKB 地址用于充值,但是该网站被黑客劫持,黑客通过篡改地址生成了一个非法地址。用户向该地址转账后,因为脚本无法解锁资金,使得转账的资金事实上被烧毁。
  • 某脚本的 args 长度可变,攻击者篡改地址后将触发不同的脚本验证逻辑。用户生成的 cell 若使用了篡改后的地址,可能会因为非预期的解锁逻辑而遭受损失。

解决方案

比特币社区提出了一个完全兼容 Bech32 的新算法 Bech32m ,该算法修复了 Bech32 的安全漏洞。新的算法使用了新的 checksum 常数 0x2bc830a3 代替原来的常数 1,对于老地址仍采用 Bech32 算法校验,而对于使用 Bech32m 生成的新地址则使用新的规则校验。

使用该方案的最大好处是 CKB 的地址格式无需修改,仅需在生成和解析校验地址时使用 Bech32m 代替 Bech32。

相关的项目库更新之后,将生成新的 Bech32m 地址,并且还能兼容老的 Bech32 地址。攻击者依然可以篡改老地址并通过地址校验,但是这种过渡是必要的,当生态中全部都开始使用新地址时,就可以对 Bech32m 库做一次升级,取消对老地址的兼容。

2 Likes

Bech32m 的实现与 Bech32 高度一致,该库的开发工作量较小。采用 Bech32m 方案需要配合修改的项目不完全列表:

  • ckb-sdk-java,Bech32m 的 java 实现 + 替换生成和校验地址的 Bech32 库

  • ckb-sdk-go,Bech32m 的 go 实现 + 替换生成和校验地址的 Bech32 库

  • ckb-sdk-js,Bech32m 的 ts 实现 + 替换生成和校验地址的 Bech32 库

  • ckb-cli,Bech32m 的 rust 实现 + 替换生成和校验地址的 Bech32 库

  • neuron,引用了 @nervosnetwork/ckb-sdk-utils 库,这个库实际上对应的就是 ckb-sdk-js。ckb-sdk-js 修改发布后,需要更新依赖版本 + 替换生成和校验地址的 Bech32 库

3 Likes

有两个问题想要请教

但是攻击者能够掌握篡改后的地址的所有权吗?
是不是有难度
还是说,其实攻击者的目的是让付款这个环节失败即可

这个如果展开说的话,指的是有可能其他账户可以因此操控用户的这个 cell 吗?

但是攻击者能够掌握篡改后的地址的所有权吗?

这种情况下,攻击者不能掌握地址所有权

指的是有可能其他账户可以因此操控用户的这个 cell 吗?

这个取决于脚本的检查逻辑。举个例子,若脚本逻辑是 args = “0x1000” 则 A 能解锁资金,若 args = “0x100000” 则 B 能解锁资金,那么攻击者通过通过篡改地址就能操纵用户的 cell

1 Like

谢谢解惑,但是 args 等于谁应该还是取决于构造脚本的人吧,他应该不至于把脚本的地址设定为hacker 所拥有的地址?

Rust 的库更新到 0.8.0+ 即可支持 bech32m

2 Likes

这两个攻击场景我觉得和 bech32 这个漏洞无关, 因为这里攻击的前提, 你也提到是能够篡改地址, 既然攻击者能够改地址了, 那哪怕用了修复漏洞之后的 bech32m, 他也照样可以改成用 bech32m 生成的其他受攻击者控制的合法地址或者被随意更改了 args 无法被解锁的任意地址

我觉得能够利用 bech32 这个漏洞的攻击场景是, 攻击者处于受限的情况, 比如说他只能通过某种受限的攻击途径给以字符p结尾地址在倒数第二个字符之前添加或者减少q字符, 从而达到篡改 payload 目的.

针对 CKB 来说, 因为短地址的 RFC 定义是有限定 args 长度的, 目前3种短地址都是 20 ( rfcs/0021-ckb-address-format.md at master · nervosnetwork/rfcs · GitHub )

只要在解析短地址的时候是有检查 args 长度的话, 就不会受这个漏洞影响, 我们需要做的是确认各种 SDK 在解析端短地址的时候有没有做这个长度的严格检查.

针对长地址来说, 因为我们的 code_hash 和 hash_type 长度都是固定的(32, 1), 受影响的只会有 args, 我会倾向于推荐一个新的长地址方案: 将 args 长度也放入长地址编码中, 定义一个新的 format type 0x08, payload = code_hash | hash_type | args_len (fixed 2 bytes) | args .

这个方案的好处是对于常用的短地址来说, 不存在任何兼容性问题, 也不需要让用户进行地址跟换升级. 对于用得比较少的长地址, 也可以做兼容性判断, 同时也能符合 ckb2021 edition 需要用到的 hash_type 包含 vm version 这个功能的需求

3 Likes

从各方面来说,这个方案最能满足目前的需求,所以就按这个方案来推进。我已经提了一个 PR RFC: propose a new full address format by rev-chaos · Pull Request #239 · nervosnetwork/rfcs · GitHub

1 Like