Zcash屏蔽地址漏洞或揭示全节点IP地址(附解决方案)
匿名币的技术之争是非常有意思的,比如KMD核心开发者Duke Leto刚发表了一篇 博客文章 ,披露了关于Zcash及其多数分叉币存在的漏洞,而这个漏洞可能会泄露使用屏蔽地址(zaddr)的全节点 IP地址。
其还为此分配了一个通用漏洞披露(CVE)编码 CVE-2019-16930 来跟踪这一问题。
(以下是其具体披露的内容)
太长不看:自Zcash和Zcash协议建立以来,所有屏蔽地址都存在着一个漏洞。它出现在所有Zcash源代码分叉中,这使得攻击者可利用它找到拥有屏蔽地址(zaddr)全节点的ip地址。换而言之,如果Alice给Bob一个屏蔽地址(zaddr)用于支付,实际上可允许Bob发现Alice的IP地址,这与Zcash协议的设计是违背的。
受影响的群体:
所有使用屏蔽地址(zaddr)以及与第三方共享屏蔽地址(zaddr)的人,例如:
- 如果你在社交媒体上公开发布了你的屏蔽地址(zaddr);
- 如果你在github/email/IRC的漏洞报告中给出了你的屏蔽地址(zaddr);
- 如果你曾把自己的屏蔽地址(zaddr)告诉了交易所、矿池或企业;
- 如果你曾在一份屏蔽备忘录中回复过你的屏蔽地址(zaddr);
如果你从未使用过zaddr,那漏洞就不会影响到你:
- 如果你只是给其他使用zaddr的人发送过钱,但从未收到过资金,你是安全的;
- 如果你使用Tor/TAILS,则IP元数据泄漏对攻击者而言就不是有价值的信息。
受影响的加密货币(非详尽清单):
- Zcash (ZEC)
- Hush (HUSH)
- Pirate (ARRR)
- 所有带有zaddr的Komodo (KMD)智能链(默认启用)
- Horizen (ZEN)
- Zero (ZER)
- VoteCoin (VOT)
- Snowgem (XSG)
- BitcoinZ (BTCZ)
- LitecoinZ (LTZ)
- Zelcash (ZEL)
- Ycash (YEC)
- Arrow (ARW)
- Verus (VRSC)
- BitcoinPrivate (BTCP)
- ZClassic (ZCL)
- Anon (ANON)
额外的说明:KMD以前也有使用屏蔽地址(zaddr),但后来禁用了该功能,Safecoin (SAFE)走了一条类似的路线,其目前也禁用了屏蔽地址(zaddr)。
缓解措施
首先,防止这种“元数据泄漏攻击”的首要方法,是在使用你喜欢的加密货币的同时,通过
-onlynet=onion
使用Tor,或者更好的选择,是使用TAILS操作系统。
其次,用户可使用全新的zaddr创建一个全新的wallet.dat(钱包文件),然后将所有资金发送到该地址。如果用户将这个新的zaddr保持为私有状态,那它就不会受到此类攻击。
Zcash在 这里 发布了一个紧急源代码(没有二进制文件)。
如果你不希望知道你的zaddr地址的人知道你的IP地址,我建议你创建新的钱包,并在软件发布更新之前停止使用旧钱包。
到这里,普通用户或许可以停止阅读了。
更多的建议
如果你运行了一个支持屏蔽地址(zaddr)的矿池,则不需要提供所有矿工和屏蔽地址(zaddr)的公开列表。由于当前的元数据泄漏攻击和其他原因,这严重地消除了矿工们的隐私。这在过去是很常见的,但由于隐私问题,大多数矿池已经停止了这一做法。
也不要在github的错误报告中给出zaddr!很多工具不断地从所有公共代码存储库中抽取潜在的敏感数据和公开来源信息。
为了提高安全性,如果你必须要给出屏蔽地址(zaddr),你可以将它们隔离到一个单独的wallet.dat,该wallet.dat通常不使用且保持离线状态,而另一个带有屏蔽地址(zaddr)的钱包可用于发送资金。由于发送资金的钱包从未发出zaddr,因此这种类型的漏洞对于该节点而言是不可被利用的。
代码分析
这个漏洞是在最初的Zcash代码库中被引入的,相关提交时间是在2016年:
“介绍新的“libzcash”zcash协议API和围绕zkSNARK电路的加密结构。”这一提交将漏洞代码添加到了较旧版本的屏蔽地址,而新版本的屏蔽地址代码都将其复制了进去。
该漏洞存在于Zcash的P2P层(从比特币继承而来)中,其中节点会与对等节点交换数据。
对攻击的解释:
- 攻击者节点将无效交易中继至其对等节点的mempool;
- 此交易对加密memo字段具有无效的序列化;
- 没有私钥且没有屏蔽地址查看密钥的节点,通常会对此无效交易做出反应;
- 具有私钥(或查看密钥)的节点将生成C++异常;
- 这个C++异常会导致不同的网络行为,从而暴露节点的“身份”;
- 区块链或浏览器上没有此类攻击的记录;
核心修改如下:
- CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
- ss << pt.get(); - SaplingNotePlaintext ret; - ss >> ret;
+ try {
+ CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
+ ss << pt.get(); + ss >> ret;
+ assert(ss.size() == 0);
+ } catch (const boost::thread_interrupted&) {
+ throw;
+ } catch (...) {
+ return boost::none;
+ }
下面这行代码没有try/catch是漏洞存在的核心原因:
ss << pt.get();
由于
pt
是由攻击者控制的数据,其正被写入本地
CDataStream ss
对象,因此需要更仔细的处理。
现在我们可以看到,只有
boost::thread_interrupted
类型的异常冒泡,所以其他异常都被“消灭”了,
boost::none
则会返回。这使得具有zaddr地址私钥的节点与所有其他节点一样,可以防止元数据泄露。
作者注意到, 这里 仍然存在寻找旧Sprout地址的易受攻击的代码。
有人可能认为这种攻击只能针对节点的对等节点,而不能针对整个网络,但增加最大对等节点计数是微不足道的,通过一个或几个节点来研究整个网络是可行的。
高级攻击者将拥有一个他们想要连接IP的zaddr数据库,然后运行密集连接至整个网络的节点,并定期向所有对等节点的mempool发送无效交易,从而建立(时间戳、Zaddr、IP地址)的三元组数据历史记录。然后,他们就可以使用这些数据,并通过数量分析和定时分析将其链接到其他数据,以完全取消屏蔽交易的匿名性,并将它们直接与IP地址和地理位置相绑定。