今年9月18号,比特币主流客户端Bitcoin Core发表文章对其代码中存在的严重安全漏洞CVE-2018-17114进行了全面披露。该漏洞由匿名人士于9月17日提交,可导致特定版本的Bitcoin Core面临拒绝服务攻击(DoS,威胁版本: 0.14.x - 0.16.2)乃至双花攻击(Double Spend,威胁版本: 0.15.x - 0.16.2)。

Bitcoin Core项目组对于该漏洞进行了及时修补,在向其他分支项目组(如Bitcoin ABC)进行了漏洞通告提醒用户进行版本升级后,公布了上段所提到的漏洞披露文章。该文章中对漏洞的成因、危害、影响版本及修复过程时间线进行了简单介绍,但未对漏洞进行详尽分析

    本文基于该漏洞披露文章及Bitcoin Core项目组在Github上的漏洞修复和测试代码,着重分析漏洞的修复方法、触发方法、漏洞成因及其所带来的危害。文中涉及测试脚本及PDF版本可于https://github.com/hikame/CVE-2018-17144_POC下载。

1. 漏洞修复

    在Bitcoin Coremaster代码分支上,commit b8f8019对这一漏洞进行了修复,如 1所示。

BitcoinCore CVE-2018-17144漏洞研究与分析-RadeBit瑞安全
图 1 CVE-2018-17144修复方法

​​    这段代码位于src/validation.cpp中的CheckBlock()函数,该函数在节点接收到新的区块时被调用。3125行调用的CheckTransaction()函数及其第三个参数的意义可以参照其代码实现进行分析

BitcoinCore CVE-2018-17144漏洞研究与分析-RadeBit瑞安全

    CheckTransaction()函数对于传入的交易消息CTransaction& tx进行检测,其中包括了检测一笔交易是否发生双花。检测方案非常简单,将这比交易中使用的所有Coin即代码中的txin.prevout,代表比特币交易UTXO,本文后续均采用Coin一词进行表述,以便与代码持一致)记入std::set中,如果发现某项记录被重复记录两次,就会返回处理失败的信息state.DoS),这一消息最终会通过P2P通道,反馈给该区块的发送者。基于代码段中的备注部分,可以看出,这段检测代码CheckBlock()函数调用过程中被认为是冗余和费时的,并通过将函数的第三个参数设置为False的方式使其跳过。

CheckBlock()执行选择跳过双花检查,由于其后续会对于整个区块中的交易进行更为复杂而全面的检查。然而,这些检查代码未能像预期的那样对某些异常情况进行检测和处置,导致了漏洞的存在。

2. 漏洞PoC

Bitcoin Core的Github上提供了实现DoS攻击的测试脚本;但要想进行双花攻击的测试,需要自己编写攻击脚本。

2.1. DoS攻击PoC

Bitcoin的master代码分支上,commit b8f8019(即前文提到的漏洞修复commit)的子commit 9b4a36e给出了该漏洞的验证代码,如 2所示。

BitcoinCore CVE-2018-17144漏洞研究与分析-RadeBit瑞安全
图 2 官方漏洞PoC

    这段使用Python编写的测试代码,位于test/functional/p2p_invalid_block.py测试脚本中。该脚本构建了一个测试网络,测试代码可以通过RPC接口、P2P接口等方式连接到目标节点,并发送测试数据,如恶意构造的区块数据、交易信息等。 2中新添加的测试代码的功能是:block2_orig区块中找到了第二项交易(vtx[2]),并将其交易输入的第一Coinvtx[2].vin[0])重复加入到了输入序列中,从而构造一个使用vtx[2].vin[0]进行双花的交易消息。如92行所示,向已被修复漏洞的node端发送block2_orig区块收到node反馈的拒绝接收消息,其消息内容即为“bad-txns-duplicate”。

    如果利用该测试的代码针对未修复漏洞的节点进行测试,则产生的效果如 3所示。由于测试脚本恶意构造的区块数据引发了目标节点的崩溃,导致了Python脚本node进程之间的P2P连接断开,使其抛出了ConnectionResetError。

BitcoinCore CVE-2018-17144漏洞研究与分析-RadeBit瑞安全
图 3 官方PoC测试效果图

2.2. 双花攻击PoC

    官方的PoC给出了DoS攻击的示意。然而,这段PoC在仅有一个node的测试网络中运行,并且所有交易数据的解锁脚本均被设定为“任何人均可花费”由于其特殊性,对于验证双花攻击欠缺一定的说服力。因此,本文基于Bitcoin Core的测试框架,自行编写了一套漏洞验证脚本。

BitcoinCore CVE-2018-17144漏洞研究与分析-RadeBit瑞安全
图 4 双花攻击网络环境示意图

    测试过程中的三个角色如 4所示。N0代表攻击者,利用Python程序所编写的恶意P2P服务,构造恶意区块数据;N1代表诸多正常节点中的一个,是N0邻居节点,两者通过P2P接口进行消息传递。测试脚本关键代码如下

BitcoinCore CVE-2018-17144漏洞研究与分析-RadeBit瑞安全

​3. 漏洞细节分析

    本文从直接导致DoS的PoC开始进行调试,这可以帮助我们快速定位问题代码的位置。利用GDB进行调试,发现发生崩溃时的代码调用栈如下(线程msghand)。

BitcoinCore CVE-2018-17144漏洞研究与分析-RadeBit瑞安全

崩溃现场代码如 5,根据函数及变量名称可以大致猜想,在进行Coin的更新过程中,会首先检查每笔交易的是否已被花费,如是,则assert失败,导致DoSBitcoin Core官方发布的客户端程序开启assert)。那么为什么又会存在双花攻击效果呢?这里需要对inputs.SpendCoin()的实现进一步的分析。

BitcoinCore CVE-2018-17144漏洞研究与分析-RadeBit瑞安全
图 5 DoS代码现场截图

3.1. CCoinsViewCache::SpendCoin()分析

    图 5中,inputs变量的类型为CCoinsViewCache类,每个该类的对象均与一个区块对应,并且在其名为base的域中存储了指向其前驱区块的CCoinsViewCache对象的指针。该类中另一个关键的内部变量为cacheCoins,存储了当前区块处理过程中新产生的或从前驱区块中查询到的Coin信息,它是一个std::map结构,key值为Coin对象的索引信息(所属交易Hash、UTXO在该交易输出序列中的序号),value值Coin的具体信息(货币数额、解锁脚本等)。

CCoinsViewCache::SpendCoin()函数实现如 6所示。该函数作用为检查outpoint所代表的某个交易的输出是否被花费过。下面将对于这三点展开详细分析。

BitcoinCore CVE-2018-17144漏洞研究与分析-RadeBit瑞安全
图 6 CCoinsViewCache::SpendCoin()代码

​3.1.1. CCoinsViewCache::FetchCoin()功能与实现

    该函数用于查询outpoint对应的交易的具体信息。 7中是该函数的实现代码:

尝试从当前CCoinsViewCache对象的cacheCoins中查询Coin信息,如存在则返回(41-43行);

1) 尝试从当前CCoinsViewCache对象的cacheCoins中查询Coin信息,如存在则返回(41-43行);

2) 1) 中未能找到,则从base所代表的前驱区块中进行交易信息的查询,查询方式是调用GetCoin()函数,该函数会进一步调用FetchCoin()函数,也就是在base->cacheCoins中查找Coin信息,当Coin信息被顺利查到,且未被花费时,返回True(45-46行);

3) 2)从前驱区块中顺利找到Coin信息,则将其加入当前区块的cacheCoins中,以备后续使用(47-52行)。

BitcoinCore CVE-2018-17144漏洞研究与分析-RadeBit瑞安全
图 7 CCoinsViewCache::FetchCoin()代码

3.1.2. cacheCoins的内容维护

对于一个区块所维护的cacheCoins,向其添加新的Coins的可能途径有两种:

1)第一种即图 8第47行所显示的,CCoinsViewCache::FetchCoin()执行过程中,从其前驱区块中查询到了相应Coin信息;

2)第二种发生在区块的交易信息中产生了新的Coin时,其对应的函数为AddCoin(),源码如图 8所示,对于一个普通的Coin(非产生于Coinbase交易),会将其记录到cacheCoins中,并于83行设置相应Coin Flag标志。

BitcoinCore CVE-2018-17144漏洞研究与分析-RadeBit瑞安全
图 8 CCoinsViewCache::AddCoin()代码

​3.1.3. Coin Flag的意义与取值

CCoinsViewCache类SpendCoin()、FetchCoin()、AddCoin()函数中均有关于Coin的Flag操作。Coin Flag存在两个状态标志位Fresh和Dirty,Bitcoin Core中对于这两个状态标志为的定义及注释如 9,可以看出:

1) Dirty标志位表示当前缓存的Coin信息与base所指向CCoinsViewCache对象所记录的Coin信息不同;

2) Fresh标志位表示这个Coin的信息在base所指向的CCoinsViewCache对象中没有记录。

    基于其描述,AddCoin()的代码 876-83行),对于一个区块中的普通交易所产生的新的Coin,其Fresh标志置1;FetchCoin()的代码中,对于来自前驱区块的Coin,其Flag在当前CCoinsViewCache对象中进行缓存时flag被置0,即既非Fresh也非Dirty的初始状态 7中第47行)

BitcoinCore CVE-2018-17144漏洞研究与分析-RadeBit瑞安全
图 9 Coin Flag的定义与注释

3.2. 漏洞触发原理分析

    在3.1中完成了对于相关代码的细节分析后,我们可以对于代码发生异常时的执行状态开展进一步的分析了。

3.2.1. DoS攻击原理分析

    攻击过程关键代码示意如下,攻击代码第4行将block2.vtx[2].vin[0]重复加入了block2.vtx[2].vin中,是实现双花的关键操作。block2.vtx[2]实际上是tx2,其构建代码如第2行所示:可以看出tx2以tx1的输出中序列号为0的Coin作为输入。而tx1、tx2在第三行被加入同一区块block2中。

BitcoinCore CVE-2018-17144漏洞研究与分析-RadeBit瑞安全

被攻击节点在接收到block2后的处理过程如下:

1) 交易tx1处理。经一系列验证分析后,该交易被认为是一笔有效交易,为了记录其输出,将调用图 8中的AddCoins()函数,该函数会在当前CCoinsViewCache对象的cacheCoins中添加一个新的Coin,并将其Flag设置为Fresh | Dirty;

2) 交易输入tx2.vin[0]处理。图 7 CCoinsViewCache::FetchCoin()代码被调用以查找对应Coin信息,1) 中的操作已将Coin信息加入当前CCoinsViewCache对象的cacheCoins。因此第43行将直接返回;而图 6 CCoinsViewCache::SpendCoin()代码会因为该Coin有Fresh标签,执行到第106行,并将其从cacheCoins中删除;

3) 交易输入tx2.vin[1]处理。图 7 CCoinsViewCache::FetchCoin()代码将再次被调用,但是,由于2)中已将相应Coin信息删除,而base->GetCoin()又无法查知该Coin,将导致46行代码返回cacheCoins.end(),进而使SpendCoin()返回False,最终触发assert失败。

3.2.2. 双花攻击原理分析

    攻击过程关键代码如下。第1行中,block1的挖矿奖励的接收者被设定为node0的地址。第二行构建的交易消息tx2即以该交易输出Coin为输入,并且重复使用了两次,而且tx2输出Coin数量挖矿奖励的两倍,是典型的双花行为。

BitcoinCore CVE-2018-17144漏洞研究与分析-RadeBit瑞安全

    被攻击节点在接收到block2的数据后的处理过程为:

1) 处理第一个交易输入block1.vtx[0]。由于该交易位于前驱节点,需要调用base->GetCoin()以获取相应Coin信息,该信息的flag被默认置0,在 6 CCoinsViewCache::SpendCoin()代码的执行过程中,将执行108-109行代码,置Dirty位,并将其余额清除,以标记已被花费;

2) 处理第二个交易输入block1.vtx[0]。由于1)中已经添加了相应的Coin信息,在 7 CCoinsViewCache::FetchCoin()代码中的43行可以直接返回该信息,但是SpendCoin()及后续代码中的执行过程中,没有对该Coin是否已被花费进行有效验证,导致双花行为没能检测出来。

4. 危害分析

    基于官方的漏洞通告Bitcoin Core的0.14.X-0.16.2版本均面临DoS攻击威胁,而且其中的0.15.X-0.16.2版本还面临双花攻击的威胁。本文基于Bitnodes网站的数据对相应版本的节点数目做了如下统计(总数为9970个节点,数据统计于2018-11-09)

BitcoinCore CVE-2018-17144漏洞研究与分析-RadeBit瑞安全

    需要注意的事,要想利用此漏洞实现攻击,其限定条件

1) 异常交易数据必须打包到区块中才能触发漏洞如果攻击者试图利用P2P接口向受害者节点直接发送异常交易数据,会触发CheckTransaction()函数中的双花检查,无法触发漏洞。

2) 攻击者必须自行挖掘出一个最新的比特币区块包含恶意交易信息的最新区块必须是有效的,否则,无法通过在交易处理之前的区块头检查。

    基于上述分析,攻击者拥有较大算力进行区块挖掘的前提下,两种攻击手段带来的危害有:

1)DoS攻击,大约可危害37%的主网节点;

2)双花攻击,需要超全网51%的算力认同恶意构造的区块,进行后续区块的挖掘。基于 1统计可知面临此类攻击威胁的节点数约32%,但由于无法统计这些节点的算力占比,所以无法确认双花攻击的危害程度

5. 总结

    本文分析的CVE-2018-17144是近年来较为少见的、存在于比特币主流客户端中的安全漏洞。此漏洞所带来的启示有:一方面,Bitcoin Core项目组的漏洞修复和处置方案有效遏制了此次漏洞带来的安全威胁,值得其他区块链项目组借鉴;另一方面,区块链节点客户端的安全是整个区块链系统安全的基石,对其开展更加深入和全面的研究是十分有必要的。