28

文章目录

  1. 摘要
  2. 背景知识
    1. 日蚀攻击
    2. 比特币的P2P网络
      1. 如何中继网络信息
        1. DNS种子
        2. ADDR消息请求
      2. 网络信息的存储
        1. tried表
        2. new表
      3. 连接节点的选择
        1. 选择表
        2. 选择IP
  3. 攻击详解
    1. 攻击方法
      1. tried/new 表IP植入
      2. 等待受害者客户端重启
      3. 理论分析结果
    2. 攻击资源需求分析
    3. 试验测量
      1. 结果的主要发现
  4. 解决方案
  5. 结束语
  6. 引用

摘要

本文给出了一种针对比特币P2P网络的日蚀攻击。攻击者只需要有足够多的IP地址就可以切断一个被攻击节点的所有对外连接,从而可以发送任意信息给受害者进行攻击。文中还仔细研究了比特币的P2P网络,进而量化了进行日食攻击所需的资源。最后,文章还提出了针对这种攻击的解决方案,其中一些已经被集成到比特币客户端里面。

背景知识

本文发表于2015年的USENIX,当时有许多文章是研究比特币的PoW(工作量证明)协议的,很少有研究比特币的P2P网络的。比特币的P2P区块链网络被设计成一种开放的、去中心化的独立于PKI的模式。这样,节点间的身份验证无需使用任何密码学机制,IP地址就代表一个节点的身份。

比特币的客户端会主动随机选择8个区块链网络中的节点与其发生连接,这些连接都是长期的连接。同时客户端还可以最多接收任意IP发过来的117个连接请求。节点间就是通过这些连接进行信息的交换的。

日蚀攻击

比特币网络的这种开放性,导致攻击者可以很容易对区块链网络中的节点进行攻击。在日蚀攻击中,攻击者会垄断一个节点发出以及接入的所有连接,从而将该节点与网络中其它的节点隔离开来。在区块链网络中,攻击者可以利用这种方式,向受害者节点发送任意信息以达到攻击的目的。

比特币的P2P网络

想要阻断区块链网络中的节点与外界的连接,必须了解比特币网络对外的连接方式。下面我们简单介绍下比特币P2P网络是如何进行网络连接的。文中描述的比特币区块链网络基于bitcoind客户端的0.9.3版本。

比特币网络中的节点使用IP地址作为他们的身份标识的。一个拥有公网IP地址的节点最多可以对外发出8个连接,接收117个连接。私有IP地址的主机只能发出8个对外的连接。网络中的连接是基于TCP协议的。比特币的网络节点通过比较VERSION消息和IP报文头来判断一个节点是否具有公网IP。下面我们介绍比特币网络是如何中继信息、如何存储网络信息以及如何选择请求连接的节点的。

如何中继网络信息

比特币网络的信息通过两种方式传播:

  1. DNS种子信息
  2. ADDR消息请求
DNS种子

比特币客户端节点在首次接入区块链网络的时候会发送一个DNS请求到DNS服务器请求现网的区块链节点的地址信息。如果失败了,则会去连接客户端内嵌的600个IP地址。另外在比特币客户端重启并尝试连接新的对端节点的时候,也有可能发送DNS请求到DNS种子服务器,发送请求的条件是以下两个:

  1. 节点尝试对外建立连接已经超过了11秒
  2. 节点少于2个对外连接
ADDR消息请求

一旦比特币客户端与一个节点建立了连接,那么其就可以发送一个ADDR请求消息给对端用于请求对端所知道的网络节点信息。节点最多会响应三条ADDR消息,并且每条消息最多可以返回1000条节点的信息(IP地址和时间戳)。

节点有两种方法将其IP地址信息发送出去:

  1. 每天节点都会给与其连接的每个节点发送一个ADDR消息,用于报告其IP地址
  2. 节点在收到ADDR消息后,如果发现其中记录的条数不超过10条,那么它会随机选择两个与其连接的节点,将这条ADDR消息发送给它们

网络信息的存储

节点使用两个表用于存储IP地址信息: tried表和new表。

tried表

tried表包含64个用于存储IP地址,每个可以最多存储64个IP地址。这些IP地址都是与节点建立过连接的(输入连接或输出连接)。对于每个IP地址,都有一个对应的最近连接的时间戳。每个IP地址都可以映射到一个桶中,桶的下表通过下面的算法生成:

def bucket_index(SK, IP, GROUP):
  	i = Hash(SK, IP) % 4
    bucket = Hash(SK, GROUP, i) % 64
    return bucket

其中,$SK$为一个随机值,$IP$为IP地址,$GROUP$为IP地址的16位前缀。由上面的算法我们可以看出,每个IP地址会映射到唯一一个桶中,每个GROUP最多可以映射到4个桶中。

节点每建立一个新的连接的时候,会将对端的IP地址插入到桶中。如果桶满了,那么会随机从桶中挑出4个IP地址,并将其中时间戳最早的那个IP:

  1. 替换为当前连接节点的IP地址
  2. 插入到new表中

如果这个IP已经在桶中,那么更新时间戳。在连接的节点发送VERSION / ADDR / INVENTORY / GETDATA消息时,会更新IP地址对应的时间戳(最小更新间隔为20分钟)。

new表

new表中包含256个桶,每个桶也可以存储64个地址。new表中存储的是尚未成功建立过连接的IP地址,主要由两部分组成:1) 从DNS请求获取的 2) 通过ADDR消息请求获取的。一个IP地址插入到那个桶中由两个因素决定:1) GROUP 2) SOURCE GROUP。$GROUP$的定义和tried表中一样,SOURCE GROUP值的是该IP地址是谁发送过来的。计算桶索引的算法如下:

def bucket_index(SK, GROUP, SRC_GROUP):
  i = Hash(SK, SRC_GROUP, GROUP) % 32
  bucket = Hash(SK, SRC_GROUP, i) % 256
  
  return bucket

由上面的算法可以看出,一个GROUP最多可以存储32个IP地址。每一个桶中存储的地址都是不同的,如果一个桶满了,那么会检测桶中的IP地址:1) 是否超过了30天;2)是否超过了最大尝试连接次数,如果满足其中一条,那么用新的IP地址将其替换。否则,新IP被释放。

连接节点的选择

节点会在启动/重启或者在发出的连接被对端关闭的时候,重新对外发起一个连接请求。选择连接节点包含两个步骤:1) 选择一个表 (tried/new)2) 选择一个IP地址

选择表

比特币客户端使用一个随机概率模型选择使用哪一个表。在选择第$\omega \in [0, 7]$个连接节点时,tried表被选中的概率为:

$$ Pr[tried] = \frac{\sqrt{\rho} (9-\omega)}{(\omega + 1) + \sqrt{\rho}(9-\omega)} $$

其中

$$ \rho=\frac{tried表中IP的数量}{new表中IP的数量} $$

简单来看,$\omega$越大,从tried表中选择节点的概率就越小。下图给出了不同$\rho$时,tried表被选中的概率。

tried表被选中的概率

选择IP

IP选择也是个概率模型,分三步:

  1. 随机选择一个桶
  2. 随机选择桶中的一个IP
  3. 这个IP被选中的概率由下面概率决定
$$ P(r, \tau) = min(1, \frac{1.2^r}{1+\tau}) $$

$r$为到当前为止,已经拒绝IP的次数;$\tau$为$\frac{当前时间 - IP时间戳}{ 10分钟}$,也就是IP保存了多少个10分钟没有更新了。显然越新被选中的概率越大。如果IP没有被选中,重新进入第一步。

攻击详解

攻击方法

攻击主要分为以下几个步骤:

  1. 用攻击者控制的IP填满tried表
  2. 用无用IP填满new表
  3. 不停攻击,直到受害者客户端重启
  4. 重新建立连接时,受害者所发出的连接只能从tried表中选择对端,这样就只和攻击者建立了连接
  5. 占据受害者的接入连接,这一步很容易实现,攻击者可以一直向受害者发出连接

tried/new 表IP植入

由于tried表会在建立新的连接时被更新,因此,需要植入该表只需要攻击者使用所控制的IP节点向受害者节点发送连接请求即可。根据替换规则,受害者客户端会选择时间戳较久的来替换,因此这个表很快就可以被攻击者IP填满。

由于客户端会将$ADDR$消息中的IP地址直接插入到new表中,而且并不会去检查这些IP是否有效,因此要填满new表只需要向受害者节点建立连接并且发送$ADDR$消息即可。$ADDR$消息中最多可以包含1000条IP,攻击者可以在这里面填写一些无法建立连接节点的IP。

等待受害者客户端重启

在很多情况下,攻击者的客户端会重启,比如:更新、系统出错等等,一旦客户端重启攻击就成功了。也有一些方法可以让客户端被迫重启,比如DDoS攻击等。

理论分析结果

这里我们简单给出一些理论分析的结果,如下图所示:

攻击成功概率

其中横坐标为$f=tried表中攻击者IP所占比例$,纵坐标为攻击成功概率,我们可以看到在攻击48小时的情况下,即使$f=0.72$,攻击也有90%的成功率。

图中的虚线表示作者实验中修改了IP替换策略:将使用较旧的IP替换改为随机选择一个IP替换。可以看到,这时候攻击成功的概率会下降。要在48小时内达到攻击成功率90%,需要填满98.5%的受害者tried表。

攻击资源需求分析

文中分析了两种攻击方式,其中一种为使用肉鸡(Botnets)进行攻击,下面我们给出在攻击者控制了$t$个肉鸡的情况下,可以占据tried表中IP地址位置的数量的图表:

肉鸡需求

从上图可以看出:表为空时,肉鸡数量需求较少。如果表不是空,那么填满tried表中同等数量的IP位置,那么采用比特币客户端的IP替换方案时(绿色线)比采用随机替换方案(橙色线)需要更少的肉鸡。显然比特币的替换方案更容易遭到攻击。

以现网肉鸡的规模来看,很容易达到攻击要求。

试验测量

文中搭建了攻击环境,对于该攻击进行了测量,结果如下。

主要测量结果

其中Infra表示那些拥有很多IP资源的机构如果想要攻击的情况,上文我们没有叙述,主要这种攻击不如肉鸡攻击简单,而是成功率也没有使用肉鸡攻击高。这里我们主要看下使用肉鸡攻击的结果。

表中各列含义为::

  • grps 表示IP地址中包含GROUP(IP地址16位前缀)的数量
  • addrs/grp每个GROUP包含IP地址数量
  • total addrs总IP地址数量
  • $\tau_t$攻击时间
  • $\tau_a$每一轮攻击时间(每一轮都需要受害者客户端重启一下)
  • Total pre-attack攻击前表中IP地址数量
  • Total post-attack攻击后表中IP地址数量
  • Attack addrs攻击后攻击者IP地址在表中的数量
  • Wins攻击成功概率

各行含义:

  • Worstcase: 最差情况
  • Transplant:这个实验环境是作者将实际运行的比特币节点移植到不同IP的服务器上进行的
  • Live:作者搭建的线上的比特币节点

结果的主要发现

在worstcase中,攻击者所的IP需求量较大,但是在此情况下,攻击成功率达到了100%。

实际攻击的成功率比预测的要高。这是由于,在前文的理论分析中,作者对于成功攻击限制较大:分析时,作者假设new表中所有IP都需要被攻击者用无用IP填满,实际攻击时并不需要这样。

在现网的测试中,作者只使用了400个肉鸡节点就达到了84%的攻击成功率。

解决方案

  1. 节点驱逐方案修改:在桶满的时候,对于替换策略进行修改:原先为从一个桶中随机选择4个,而后将最旧的替换;改为,将IP地址也采用和桶一样的固定映射策略(使用哈希计算其在桶中的索引)。这样攻击者就不能反复使用同一个IP进行填充(因为其被映射到了同一位置)
  2. 节点选取方案选择:之前在节点选取时,节点倾向于选择较新的节点来进行连接,这样容易被攻击者利用;改为随机选取连接节点
  3. 驱逐前测试:在选择替换一个IP地址时,先测试一下,这个节点能否被连接,如果可以就不进行替换,即使其时间戳较旧。这就极大加强了攻击者的IP注入的难度
  4. 连接感知:随机选择new表中的一些IP地址建立短的连接,如果成功,将地址加入到tried表中
  5. 锚连接:建立一个锚连接表,用户记录节点对外发出的成功连接。每次重启的时候,从这个表中选择两个IP进行连接。这样攻击者,必须首先攻破这个锚连接表才能攻击成功

其它还有一些解决方案也可以帮助防御日蚀攻击,比如:增加桶数量、发出更多的连接、禁止主动输入的$ADDR$地址等等,具体可以参考论文。

结束语

日蚀攻击对于比特币网络的稳定性影响还是比较大的,日蚀攻击可以被用来进行许多其它的攻击,比如双花攻击、算力浪费、使用较少的算力操控比特币网络等等。

本文针对比特币的P2P网络,建立了日蚀攻击的模型,对其进行分析,并且在现网进行了实验分析。实验给出了攻击成功需要的成本,在最后文章还给出了一些解决方案,其中一些已经被集成到区块链网络中去。

引用

[1] Heilman, Ethan, et al. "Eclipse attacks on bitcoin’s peer-to-peer network." 24th {USENIX} Security Symposium ({USENIX} Security 15). 2015.