启明星辰ADLab:以太坊智能合约多个攻击案例分析

发布时间 2018-08-25

 近日,启明星辰ADLab使用最新上线的智能合约监控系统发现了大量以太坊智能合约的攻击事件。在众多攻击案例中,有些漏洞成因或攻击模式少有研究涉及,也出现了一些比较隐蔽的攻击链。本文将对这些攻击案例进行详细分析。


一、使用Oraclize服务的疏忽


        为了将区块链技术应用到线下,例如将飞机延误险、数字货币兑换等业务上链,区块链需要具有访问链外数据的能力。但是如果智能合约直接从外部服务获取数据,由于网络延迟,节点处理速度等各种原因,会导致每个结点获取的数据不同,使区块链的共识机制失效。


        现有的解决方案是使用第三方发送区块链的交易,交易会同步到每个节点,从而保证数据的一致性。Oraclize是一个预言机,为以太坊等区块链提供数据服务,它独立于区块链系统之外,是一个中心化的第三方。Oraclize可以提供的数据访问服务包括随机数、URL访问、IPFS等。Oraclize的架构如图所示:





        Oraclize不是链上直接可以调用的函数,而是一个链外的实体。为了抓取外部数据,以太坊智能合约需要发送一个查询请求给Oraclize,当Oraclize监听到链上有相关请求时,立即对互联网上的资源发起访问,然后调用合约中的回调函数__callback将查询结果返回区块链。


        例如,用美元兑换以太币的智能合约的数据查询语句如下:



        监听到请求后,Oraclize会访问URL获得查询结果,然后调用__callback的函数,Oraclize返回的数据通过__callback函数参数传回智能合约。上图中函数调用的参数[3]中的“3334312e3533”即为当时的汇率:1ETH = $341.53,随后智能合约会根据这个查询结果进行后续的逻辑处理。



1、攻击案例:SIGMA (0x03AF37073258B08FfFF303e9E07E8a0B7bfc4fd9)


        SIGMA合约使用了Oraclize服务查询汇率。该合约的__callback回调函数如下:



         由于__callback函数中存在整数溢出,导致owner的代币余额被下溢成一个很大的值,导致代币增发。从代币份额排名可以看出攻击者的账户地址为0x2ef045a75b967054791c23ab93fbc52cc0a35c80,而该地址并不是创建合约的账户地址(0xC7e92D8997359863a8F15FE87C0812D7A3a8F770)。



        跟踪Transactions,发现0xC7e92D8997359863a8F15FE87C0812D7A3a8F770调transfer_ownership将合约的owner设置为0x2ef045a75b967054791c23ab93fbc52cc0a35c80。



        针对这个漏洞是否使用SafeMath就可以解决了呢?答案是否定的。在Oraclize调用__callback之前,有用户对查询函数的调用,而且这个调用花费以太币。



        使用SafeMath的情况下,发生溢出的事务会回滚,但本例中能够回滚的只有Oraclize对__callback函数调用的事务,而之前用户花费以太币发生的事务则无法回滚。这个现象的根本原因是Oraclize是一个独立的实体,导致逻辑上应该完整的一个操作被分割成了两个事务。因此,通过Oraclize与链下数据交互时只能更加小心,代码编写需要更加谨慎。


二、庞氏代币合约漏洞


        以太坊智能合约中混杂进了不少庞氏骗局合约,他们向投资者承诺,如果你向某合约投资一笔以太坊,它就会以一个高回报率回赠你更多的以太币,然而高回报只能从后续的投资者那里源源不断地吸取资金以反馈给前面的投资者。


1、攻击案例:ETHX( 0x1c98eea5fe5e15d77feeabc0dfcfad32314fd481)


        ETHX是一个典型的庞氏代币合约。该合约可以看成虚拟币交易所,但只有ETH和ETHX (ERC20 token)交易对,每次交易,都有5%的token分配给整个平台的已有的token持有者,因此token持有者在持币期间,将会直接赚取新购买者和旧抛售者的手续费。从ETHX合约代码可以看出,该合约对transferFrom函数进行了扩展,transferFrom函数首先进行allowance限额判定,然后调用了自定义的transferTokens函数来完成转账。



        在transferTokens函数中,当to账户地址不等于合约地址,由于事先对from账户额度进行了安全检查,因此后面对from账户的balance运算不会产生溢出。



        当to账户地址等于合约地址时,则调用sell函数,sell函数中由于代码编写失误,错误的将from写成msg.sender,对msg.sender的额度进行了减法操作,而在减法操作前没有进行安全检查,因此存在溢出漏洞。



        为了完成对这个溢出漏洞的攻击,攻击者需要2个账户A、B,其中A账户代币余额不为0,B账户代币为0。


        • A账户调用approve给B授权一部分转账额度,假设授权额度为1;


        • B账户调用transferFrom,从A账户转1单位代币到智能合约;transferFrom调用sell函数时触发整数溢出,即0-1=2^255。B账户在余额为0的情况下获得了最大额度的token。


        在ETHX合约攻击链中,攻击者使用了两个账户地址,分别为:


        0x423b1404f51a2cdae57e597181da0a4ca4492f30


        0x17a6e289e16b788505903cc7cf966f5e33dd1b94


        首先,0x17a6e289e16b788505903cc7cf966f5e33dd1b94调用approve给0x423b1404f51a2cdae57e597181da0a4ca4492f30授权转账额度,参数value=1。


        然后,0x423b1404f51a2cdae57e597181da0a4ca4492f30调用transferFrom方法,从账户0x17a6e289e16b788505903cc7cf966f5e33dd1b94向ETHX合约地址0x1c98eea5fe5e15d77feeabc0dfcfad32314fd481 转移1个Token。



        调用前,balance(0x423b1404f51a2cdae57e597181da0a4ca4492f30)=0。调用后,溢出后balance(0x423b1404f51a2cdae57e597181da0a4ca4492f30)=2^255。


        监控平台显示已经被攻击的同类代币合约如下表:



三、SafeMath使用不当


        以太坊虚拟机EVM定义无符号整数为uint256,可以表示一个256位的大整数,但并没有提供溢出的检测机制。OpenZeppline是一个第三方智能合约库,实现了一套SafeMath库来检测溢出。其代码如下:



        SafeMath使用内建的require或assert来检查运算是否发生溢出,如果发生了溢出,require和assert中包含的代码会使该事务回滚。但有些开发者不能完全理解SafeMath模版代码,导致合约代码中仍然存在漏洞。


1、攻击案例:UCN (0x6EF5B9ae723Fe059Cac71aD620495575d19dAc42)



        UCN(http://www.saveunicoins.com/Unicorn/index.html)是一个智能合约DApp应用。合约代码在SafeMath库中注释assert语句,因此SafeMath函数等同于直接进行算术运算,没有任何安全检查。并且在transferFrom函数中,注释中声明sub函数是安全的,不知道这是开发人员的疏忽还是故意留下的后门。



        由于sub函数等同于算术运算,balances[_from] = balances[_from].sub(_value);


        存在整数下溢漏洞,可以使得账户余额变成一个极大值。


2、攻击案例:EMVC(0xd3F5056D9a112cA81B0e6f9f47F3285AA44c6AAA)


        EMVC(http://crypto7.biz/)合约代码在SafeMath库中使用了一个自定义的assert来代替内建的assert。在assert函数中,如果参数assertion为false则直接return,并没有进行异常处理。因此SafeMath函数等同于直接进行算术运算,没有任何安全检查。



        攻击者可以使用transfer函数设置任意账户余额为任意值。



四、总结


        当智能合约要实现更多功能时,代码会相应变得更加复杂,与ERC20标准代码的差异也越来越大,因而潜在的漏洞面貌更加多样。为了保证智能合约的安全,除遵循安全开发原则、按照“Check Lists”进行基线检查外,还需要实施更深入细致的审计。