漏洞复现 | Beosin 提交Circom 验证库漏洞CVE-2023-33252,并附修复方案,zk项目方需警惕!
此前,Beosin 安全研究人员在 SnarkJS 0.6.11及之前的版本的库中发现了一个严重漏洞,当该库在验证证明时未对参数进行完整的合法性检查,使得攻击者可以伪造出多个证明通过校验,实现双花攻击。SnarkJS 是一款用于构建零知识证明的开源 JavaScript 库,广泛应用于 zk-SNARK 技术的实现和优化。
Beosin在提了这个漏洞以后,第一时间联系项目方并协助修复,目前该漏洞已修复完成。Beosin提醒所有使用了SnarkJS库的zk项目方可将SnarkJS更新到 0.7.0版本!以确保安全性。
现在,Beosin以长文的形式与大家分享漏洞原理,以及提醒项目方需要注意什么。
Circom是基于Rust开发的零知识证明电路编译器,该团队同时开发了SnarkJS库用于实现证明系统,包括:可信设置、零知识证明的生成和验证等,支持Groth16、PLONK、FFLONK算法。
https://docs.circom.io/
2. 漏洞原理
Beosin安全实验室漏洞研究人员在SnarkJS小于等于0.6.11版本中发现,当该库在验证证明时未对参数进行完整的合法性检查,使得攻击者可以伪造出多个证明通过校验,实现双花攻击。数学依据:首先如果要在以太坊中生成和验证zk-SNARK证明,需要使用 F_p-arithmetic 有限域椭圆曲线电路,其中曲线的一般方程如下:
可以发现曲线上的点都会进行一个模p运算,所以电路生成的证明参数s值取值范围为[0,1,…,p-1],那么当SnarkJS的变量范围大于电路取值范围时,存在下列多个具有相同输出的证明参数值:
综上,只要知道了其中一个合法的证明参数s,参数范围内的s+np( n = 1,2,…,n)都可以满足验证计算,于是攻击者在获取到任意验证通过的s,即可构造多个s可以通过校验,具体的攻击流程如下:
上文可知,参数的取值范围由p决定,而不同类型的F_p对应不同的p,需要根据具体使用的零知识算法确定。
3. 漏洞实现
使用snarkjs库进行链下验证时,在groth16Verify函数中并未校验publicSignals参数的取值范围合法性,导致可以伪造证明通过校验:
4. PoC
初始的originalHash验证通过,接着使用刚伪造的attackHash同样验证通过!即同一份proof,可以被多次验证通过,即可造成双花攻击。
此外,由于本文使用ALT_BN128 曲线进行复现,因此共计可以生成6个不同参数通过验证:
4. 修复方案
Circom 项目已经针对该通用漏洞进行了修复,涉及到其实现的共计3个算法:
https://github.com/iden3/snarkjs/issues/358
1)groth16_verify.js
https://github.com/iden3/snarkjs/commit/7f462cc932191348a057e6e18f377154369104fc#
2)flonk_verify.js
3)plonk_verify.js
针对此漏洞,Beosin安全团队提醒zk项目方,在进行proof验证时,应充分考虑算法设计在实际实现时,由于代码语言属性导致的安全风险。同时,强烈建议项目方在项目上线之前,寻求专业的安全审计公司进行充分的安全审计,确保项目安全。
5. 漏洞后续
目前该漏洞已经被收录到github advisory database中,且为评分7.5的高危漏洞:
https://github.com/advisories/GHSA-xp5g-jhg3-3rg2
同时该高危漏洞也被更新到npm库中,安装旧版本snarkjs库时会有如下预警信息: