今天我将分享一些过去几年区块链遇到的攻击实例,为大家展示一下不太一样的区块链安全和隐私问题。
第一,恶意攻击。这些攻击大多是大家平时接触到的区块链相关的智能合约攻击。
第二,理性攻击。平时可能比较少接触到,也就是攻击者如何利用游戏规则之内的一些方法将自己的利益最大化,具体会以矿池和矿工为例。
第三,隐私攻击。会根据针对Monero和Zcash的攻击简单介绍一下目前隐私区块链系统存在的问题。
在过去几年中,从比特币到以太坊,区块链系统从最初的分布式账本功能,慢慢进化到现在类似于分布式计算机。初期的比特币大家只能用于在线的电子支付或者跨境支付,但是现在有了各种各样的智能合约以及链上的运行逻辑,大家可以轻松的在以太坊以及其他区块链平台上进行编程和运算。
如上图,大家可以看到不同的节点目前其实是蕴含对智能合约等其他程序的处理能力。左边的用户可以使用密钥进行数据签名,然后发送交易给节点,节点处理这些交易之后会更新对应的状态生成区块广播给其他节点,其他节点会进行运算和验证然后写入本地的区块链。这样的分布式系统环环相扣,保证了不同国家、地区的用户可以在这台世界计算机上面进行操作和运算。
功能这么强大的分布式计算机听起来很酷,但是从安全角度来看,当一个系统支持的逻辑功能越多,其实它的安全隐患是越多的,这也是为什么比特币系统的安全性相对以太坊要好很多的一个原因。
参见以太坊的重大安全事件和,大家可以发现,如果一个区块链平台上的安全问题频频曝出,也会打击大量的开发者以及一些创业公司在平台上部署商业应用的信心。
当我们去看另一个区块链平台EOS,明显可以感觉到对应的安全事件,其实是跟对应的价值关联度更高。不管是链本身还是智能合约层出不穷的安全问题,整体使得大家更加倾向于选择去中心化程度高,以及更加安全的区块链平台去部署自己的商业逻辑。
过去一年有各种各样的DeFi攻击事件,其实也为大量的区块链开发者敲醒了警钟,当上线各种新功能之前一定要做好安全审计,尽量保证自己上线的功能是没有较大的安全问题。
恶意攻击
接下来我们看一下恶意攻击,特别是与智能合约相关的攻击。
重入攻击
2016年的DAO问题以及最近的DeFi问题,都是类似的攻击。攻击者编写了对应的恶意智能合约,调用受害者的合约,同时利用自己的回调函数,循环地调用受害者合约的代码。由于是重复进入受害者合约执行对应的一段代码导致漏洞,所以把它叫做“重入攻击”。
如上图,右边是恶意智能合约,左边是正常的智能合约。正常的智能合约如果按照左边的逻辑,其实应该从上到下进行withdrawBalance,然后以太坊转账,最后更新对应用户的余额。
然而,恶意合约的执行逻辑则是,首先发起Withdrawal的函数,调用withdrawBalance函数,之后左边正常的智能合约会调用对应的withdrawBalance,以太坊转账,当转账一旦完成,由于以太坊本身的特性,就会调用恶意智能合约里面的回调函数,回调函数就会进一步的调用左边的withdrawBalance。
整体来看,按照这个箭头一二三四,形成了一个循环。导致的后果就是恶意合约在发起调用之后,可以一直不断、如此往复地进行转账,把左边智能合约里的余额源源不断地转到攻击者的地址,只要左边智能合约的燃料费是足够的以及还有余额,最终会把所有余额转给对应的攻击者。
这是一个简略的流程图,大家可以看一下实际代码,左边是正常的智能合约,右边是攻击者的智能合约。攻击者智能合约,在右上角进行发起,调用左边withdrawBalance函数,之后调用call.value(),call.value()就触发了恶意智能合约的默认payable的回调函数,默认回调函数会进一步的再触发循环,直至整个左边智能合约所有余额都转给恶意攻击者。
由于重入攻击,The DAO的合约在当时损失了超过6000万美金的ETH。区块链有不可篡改的特性,这样的交易是不能回滚的,造成的损失是永久的。当时社区有想修复这样一个漏洞来退回对应损失的以太,也有一些社区成员是不想的,最终导致了以太坊当时的分叉,分叉出了现在大家熟知的ETC/以太经典。
前事不忘后事之师,然而很多时候大家都会遗忘历史,即使是跟安全息息相关的。最近的DeFi或者LendF.me的攻击中,又见到了类似2016年的重入攻击。由于过程其实比之前2016年攻击要复杂很多,这里就简化说明一下。
Lendf.me押金函数被回调函数中的withdraw反复调用,导致了智能合约中的攻击者抵押的总额是在没有足够抵押金的情况下持续上升的。相当于通过一直调用这样的循环,让攻击者本身的抵押总额一直上升,即使他没有对应的抵押物,最终导致攻击者积攒了足够多imBTC对应的抵押金额。之后攻击者借出所有可用的资产,超过2500万美金。当然,比较幸运的是通过一些方法攻击者返回对应的资产,也是不幸中的万幸。
重入攻击是很严重,防御措施是怎样呢?其实在做转账之前可以先把内部的状态设置好,例如当智能合约要给某个用户转10个ETH,在转账之前提前把用户对应的余额变量,减掉对应的10个ETH再做交互转账,就可以避免这样的攻击了。这就是一个比较好的规范,先去检查,然后执行,最后再做交互。
溢出攻击
另一个想给大家分享的攻击也是过去几年中比较普遍出现的攻击,叫做“溢出攻击”。大家如果是程序员都知道,不管是用C++还是JavaScript,当开发者操作算术运算不当就会出现越界,特别是整数溢出。当超出整数类型的最大范围的时候,数字会由极大值变为极小值或者直接归零,叫做“上溢”;相反的超出最小值范围叫做“下溢”。
上图是一个样例。可以看到攻击者发了好多交易,其中_value的对应值特别大。具体到257行,当_value特别大的时候,cnt如果大于2,amount就直接向上溢出为零,导致之后的安全检查全部通过。259行的第二个检查正常来说是不应该通过的,但是由于溢出的amount变成0了,左边balances[msg.sender]的数值肯定大于等于零,导致第二部分的安全检查也通过了。最终攻击者成功获取超大数目的数字资产,即使他本身的余额可能会极其小。
其他的一些例子大家可以查看multiOverflow (CVE-2018-10706),transferFlaw (CVE-2018–10468), proxyOverflow (CVE-2018-10376)。攻击者通常都是利用智能合约的溢出漏洞,制作出可以造成溢出的交易,之后通过溢出的变量来通过智能合约的安全检查,最终获取巨大的资产。作为防御措施,开发者尽量使用保证计算安全的库,比如SafeMath。
所有权攻击
所有权攻击是指智能合约的函数的所有权被攻击者篡改。在一些攻击中,有些函数设置了只有Owner才能看的访问控制。然而,谁可以作为Owner呢?这个函数没有实施访问控制,任何人可以发送交易给智能合约把自己设置成Owner,然后就可以操纵其他函数。
还有一个例子是任何人可以通过调用公开函数,然后将自己的地址设置为CEO的地址,一旦你的地址变为CEO地址,你就可以进行CEO对应权限的操作,也就可以有很高的权限进行任何操作了。所有权攻击利用了程序开发时访问控制设计的漏洞,进行对应的攻击。
拥塞攻击
除了利用智能合约漏洞的攻击外,还有些攻击其实是利用区块链本身的特性进行攻击。其中最有意思的一类攻击叫做“拥塞攻击”。比如Fomo 3D,前几年Fomo 3D是很流行的彩票游戏,最终获得钥匙的人可以获得该合约的几乎所有ETH。游戏规则是怎样的?在每一轮限定时间内大家都可以买钥匙,每轮钥匙价格会持续增高。如果在限定时间内有用户参与买钥匙,那么下一轮就会继续进行。直至当前轮在限定时间内没有人买钥匙,那么上轮的买到钥匙的用户就是最终赢家。
Fomo3D的第一轮赢家也是攻击者,他获得了超过1万以太的奖励。正常情况下,游戏规则是挺公平的,最后一轮用户获得最终奖励。然而,攻击者发现了以太坊本身的拥塞问题,通过自己的操作获得了更高的成功空间,让自己变成了最终的赢家。
这是以太坊什么样的漏洞呢?其实这个也不算以太坊的漏洞,是它的特性。
首先,以太坊本身的吞吐量不是特别高,每秒只能处理10到20个交易,当然现在相对高一点,未来以太坊2.0可能会更高。
然后,每一个区块都有对应的燃料限制,为了保证以太坊的节点不受拒绝服务攻击。赢家、攻击者利用了这两个特性:吞吐量低、有燃料限制。他制作了大量高手续费和可以消耗尽燃料的交易,使得其他对手交易不能被智能合约处理,最终只处理自己超高手续费的交易,保证自己在最后一轮拿到钥匙,接下来一轮别人的交易进不去,在限定时间内他就变成了最终赢家。
攻击者在自己拿到钥匙以后就发送大量消费燃料和高手续费的交易,导致接下来的区块只处理很少的交易。超高的手续费使得赢家可以让自己的交易被节点进行打包,当节点只处理超高手续费交易的时候,燃料也耗尽了,其他交易进不来了。最终攻击者就变成了最后一轮获胜的赢家。
如果增加平台吞吐量会不会有改进?攻击者会不会完全没办法进行攻击?其实不然。大家可以看另一个区块链平台EOS,EOS上有一款EOSPlay游戏。这款游戏利用未来区块的哈希值进行随机数计算。通常情况下用户是不清楚未来有哪些交易放入区块链的,然而在拥塞的情况下,攻击者是可以操作未来区块包含的交易,提前计算出随机数。实际攻击中,攻击者在CPU价格很低的时候买入大量的CPU资源,生成大量交易,让对应的智能合约处理交易。
由于攻击者购买大量运算资源,导致当时的EOS平台有比较大的拥塞,最终导致智能合约和节点在进行运算时只有攻击者交易是被写到对应的区块里的。这些区块里的交易由于是攻击者制作的,攻击者可以进行简单的排列组合,然后对排列组合里交易进行计算,提前算出可能出现的随机数,之后获得远超于其他正常用户的优势。最终攻击者花费了大约21万EOS,但是获得了超过24万EOS,收益大约2.8万EOS。
针对这些恶意攻击,有哪些防范措施?
1、在开发链上逻辑的时候按照一定的安全范式,比如先改变变量再调用外部智能合约。
2、使用成熟的安全库,例如SafeMath库,尽量不要自己造一些不太熟悉的底层库轮子。
3、对关键业务操作要使用加锁机制,设置暂停开关。好处是即使未来发生黑天鹅事件或者被攻击,可以暂停自己的智能合约,减少经济损失。
4、不要使用链上数据做随机数,因为攻击者很可能对未来区块里的信息做文章,导致随机数可以被操纵。
5、在上线任何功能之前做第三方安全审计(至少两家)。
6、当部署自己的链上逻辑后,需要对对应的智能合约进行链上监控。这样可以及时发现问题,尽早解决。
上面提到的攻击,不管是重入攻击、溢出攻击、网络拥塞攻击,攻击者的意图是恶意的。然而,还有一些攻击是非恶意的,只是想把自己的利益最大化。这也是区块链系统本身特有的攻击发展方向,有点像传统经济学、金融学上的博弈论。
理性攻击
下面我主要介绍一下区块扣押攻击、验证者两难攻击。
区块扣押攻击
先从挖矿说起,大家都知道比特币本身是使用工作量证明挖矿的。通过计算哈希值,看前面有多少位是“0”,如果前若干位是“0”那么矿工就挖出了新的区块。如果矿工想独自挖出新的区块,硬件投入是相当大的,可能要有超过上亿美金的投资去买矿机。
不同的矿工由于不能确定自己能独立挖到区块,就会联合起来形成不同的矿池。矿池的优势是会把难度进行分解,把大问题分解成小问题。
像Nonce,可以把“11”开头的分给一个矿工,把“10”开头的分给另一个矿工,每个矿工完成对应的计算后,把对应的结果返回给矿池的管理人。目前超过90%的矿工加入矿池,矿池的存在使得矿工收入更加稳定。例如你这边有90个矿工,其中1个矿工挖到了最终区块,那其他90个矿工可以按照自己的算力拿到对应的奖励。
比特币联合挖矿协议看起来特别合理,矿工的算力高,拿的奖励就高。对于矿工来说,遵守协议,直观来说收益应该是最高的。
针对这样的协议有没有方式进行攻击呢?这里就要提到区块扣押攻击。在最初提出“区块扣押攻击”的时候,大家都不以为然,很多矿池运营人员都感觉不是一个大的问题。然而,之后却发生了矿池受到扣押攻击,造成了超过20万美元的损失,让大家意识到原来区块扣押攻击是真实存在的。
图中大致介绍了矿工是可以参与到不同的矿池里,可以参加左边的矿池,也可以参加右边的矿池,不同的矿池有不同的收益,根据你的算力获得对应的奖励。矿工其实可以把自己的算力资源分配给不同矿池,最终想要达到的效果就是利益最大化。那么,如何将整个算力资源进行合理分配,然后利益最大化?
刚刚提到扣押攻击就是一个例子。作为一个正常矿工,只要按照矿池分发不同的任务进行运算,把结果提交给矿池就可以了。然而区块扣押攻击是即使挖到获得比特币的区块也不汇报给矿池,只是将之前算力算出来无效的结果给矿池进行汇报。由于矿工把之前算出来结果都给矿池进行分享,矿池还是按照矿工的算力支付对应的奖励。
比特币是零和游戏,这样做会不会让其他矿池获利更多,自己有损失呢?
现在请大家拿出纸和笔计算一个很简单的例子,攻击者拥有全网25%的算力,受害者矿池有75%的算力,对自己的算力进行分配,将5%的算力分配给矿池,剩下的20%自己挖矿。接下来开始区块扣押攻击,攻击者在矿池占了5%的算力,但是5%的算力其实是磨洋工,即使算到了对应的区块,也不告诉矿池,所以矿池拿到的结果都是无效的。由于按照目前的协议,矿工只要算出来对应的结果给矿池,就可以拿到对应的奖励,所以矿池还是会按照5%的算力分发奖励。
但是由于这5%是磨洋工,全网的真实算力只有95%,对应的攻击者占了21%,矿池占了79%。按照协议,右边矿池拿79%奖励,其中79%*5%=4.9%会分发给磨洋工的攻击者。攻击者本身正常挖矿也获得了21%的奖励,加起来25.9%。总结起来,如果攻击者正常按照协议运行,25%的算力只能获得25%的奖励,但是如果实施区块扣押攻击,他就可以获得25.9%的奖励。
对区块扣押攻击进行进一步分析,例如考虑不同的情况,单一攻击者,单一矿池;单一攻击者,多个矿池;多个攻击者,多个矿池。当进行大量运算后,我们发现如果攻击者算力越高,获得的奖励越高。同时,如果只是单独攻击一个矿池,奖励没有攻击多个矿池结果高。这和我们平时的直觉不太一样,我们通常会感觉严格地遵循游戏规则会获利最大化。但是在这里大家可以看到,矿工稍微改变挖矿方式实施区块扣押攻击,可以获得更多的收益。
针对扣押攻击,有哪些解决方案?其中一个方法是将同一任务分给多个矿工,一个矿工作弊,另一个矿工如果挖到比特币的区块会向矿池汇报。也可以改变偿付计划或者方式,例如给挖到比特币区块的矿工更多的奖励,鼓励矿工按照协议进行挖矿。当然,也可以改变比特币的协议,使得原生挖矿可以支持矿池挖矿。
验证者两难问题
下面分享一下验证者两难问题。
曾经有一段时间,比特币5%的矿工开采了无效区块。大家发现原来很多矿工在没有验证区块的情况下进行挖矿,导致越来越多人在无效区块上建设新区块,浪费了对应的算力,更容易导致51%攻击。
像比特币、以太坊,矿工的奖励大部分是通过工作量证明获得的,只有很少一部分是打包交易中的手续费。然而在做哈希运算之前一个矿工要验证对应每个区块是否正确、交易是否正确,矿工作为验证者是没有任何奖励的。这导致了矿工在进行挖矿时认为做哈希运算是更重要的。资源枯竭攻击又加强了矿工的这种想法。
在资源枯竭攻击中,攻击者将一些消耗大量运算资源的交易发送给矿工,使得按照协议进行正常验证交易的矿工花费大量时间进行验证,而没有验证这些交易同时直接打包交易计算哈希挖下一个区块的矿工有了巨大的时间优势(例如之前攻击中验证需要超过3分钟)。
这就导致了“验证者两难问题”。如果矿工按照协议验证交易和区块,那么他可以保证自己挖的区块是没有问题的,但同时可能由于验证的时间过长,导致自己的挖矿时间晚于没有做验证的矿工,处于挖矿劣势;而没有做验证的矿工,会提前打包交易有时间上的优势挖下一个区块,但是自己的区块可能存在问题,问题区块未来会被其他矿工或者节点丢弃造成自己算力的浪费。两难选择导致了矿工验证者困境,到底应该验证还是不验证,如果大家都不验证,那么系统更容易受到51%攻击。如果有些人验证,有些人不验证,不验证的人会有更大的优势去寻找下一个区块。目前对于验证两难问题,还没有比较好的解决方案。
隐私攻击
最后,简单介绍一下针对已有的隐私区块链系统的攻击。
匿名性是指在一组对象(匿名组)里不能很好地单独区分对应的对象。在比特币的交易中,A转账给B,大家可以很明显地看出来这笔交易的接收者是B。我们可以通过一些手段对接收者进行隐藏,例如Monero可以将A发给B的交易和其他交易(“Mixins”)进行混合,那么大家就不能明确地区分出接收者是B还是CDE。通过将不同的交易进行混淆,让大家不清楚到底A是发给B还是CDE。
早期Monero没有强制每个发送者都设置安全条件保护用户隐私。这导致有些粗心的用户以为Monero的隐私性是默认的,但实际没有。而且由于匿名组不够大,通过一些简单的推算分析可以很快推算出很多交易的接收者。如图最下面的紫色箭头,C只混合了两笔交易,由于上面绿色箭头A已经发给B了,接下来C就不可能发给B,肯定是发给D。中间的黑色箭头虽然混淆了很多交易,但是由于之前的几笔交易已经暴露出来,我们可以推算出最终的接收者是E。
在过去一段时间里,特别是早期Monero系统里,有超过64%的发送者没有使用Mixins设置多个接收者,超过63%的发送者即使设置了多个,但是可以根据刚刚的方法推算出接收者。当然,随着Monero隐私保护功能的逐步增强,用已有的攻击方法是更难推算出来接收者的。
大家都知道Monero本身的隐私性不是特别好,在区块链世界里谁的隐私性相对最好?就是Zcash,Zcash是使用零知识证明来保护发送者和接收者的身份以及发送金额。
图中有几种特殊交易:透明交易,A发给B,大家知道数目,也知道发送者、接收者。之后B发给C,进行匿名化。由于有匿名池,C再发给DEFG的匿名交易,大家都分辨不出来到底谁是真正的接收者。但是,DEFG有时候可能要去匿名化,把匿名身份转为非匿名身份。
有意思的是Zcash里超过85%的交易都是透明的,入金/出金很容易推算出来的。有少于1%的交易是匿名交易的,Zcash本身使用零知识证明,目前来讲零知识证明是比较安全的匿名化方法。
很多研究者、攻击者会把精力放在怎么通过匿名化、去匿名化交易推断出地址关联性。有哪些人在使用匿名交易池呢?
1、矿工,分独立矿工和矿池。由于Zcash每个区块有10ZEC的奖励,那么很容易区分出区块的奖励者、接收者的地址。
2、创始人。每个区块有2.5ZEC奖励,地址是公开的,大家也可以区分出来。
3、个人用户等。看入金进入匿名交易池的地址是很容易区分的,有大于80%的入金匿名化交易来自矿工,创始人也有很清晰的交易步骤。矿工直接从区块拿到奖励,地址相对容易暴露出来。从匿名交易池出来的地址不容易区分,大于90%去匿名化的交易地址很难区分。
通过一些分析,攻击者发现大家使用Zcash匿名化的过程中有一些很有意思的操作,例如创始人行为。很多情况下创始人入金都是249.999ZEC,出金是250.001ZEC。如果仅从对应的数值来看,大家很容易区别出哪些地址在进行匿名化操作时候是来自创始人的,哪些去匿名化的地址也是来自于创始人的,因为数值可以进行绑定。
矿池也有一个很有意思的行为,矿池会把资金转入匿名交易池,然而在分给矿工奖励的时候最终还会有自己的地址。这导致了很容易辨认的模式,攻击者可以分析出已知矿池地址,同时还有上百个其他地址,那么这笔交易肯定是属于矿池和矿工的。
不管是Zcash还是Monero,很少人能够很好地使用隐私保护系统以及手段。很多时候大家觉得自己用了隐私保护系统,那么保护效果就应该很好。但其实不是的,每个系统都有一些对应的设置,如果你作为用户没有设置好的话,那对应的交易是不能被很好地保护起来的。
针对Zcash有69%的匿名化交易是可以根据刚刚的模式区分出来,但是Zcash的底层密码学技术是安全的,可以提供很高的匿名性以及隐私性。前提是大家能够比较正确地进行匿名化操作,保证自己没有使用那些明显的模式,而被攻击者、研究者发现出来。
“在区块链的世界里,除了有破坏规则的恶意攻击者,还有很多利用规则的利益最大化者。”
例如区块扣押攻击以及利用区块链平台的拥塞攻击。大家没有破坏规则,只是利用区块链本身的规则/平台特性来达到利益最大化。我们在设计区块链系统和应用时,最好能够兼顾自己的系统或者程序是没有漏洞给恶意攻击者进行破坏,同时尽量防止利用协议/规则的损人利己的利益最大化行为。
最后,希望与大家一起创造出更好的方法,共建安全的Web3.0生态。
End
作者:贾瑶琪,Parity亚洲技术总监