以太坊智能合约安全

引言

智能合约就是自主执行的合约,其条款是用代码规定的。html

虽然这个概念已经存在一段时间了,但至少从1996年Nick Szabo提出了这一律念以来,直到图灵完备的以太坊区块链来临,智能合约的使用才变得广泛。git

以太坊的智能合约存在于合约地址里,能以交易命令来调用。用代码编写并存放在不可更改的公链上的合约执行起来会产生必定的风险与安全问题。咱们将会在本文中讨论这些问题和可能的缓解措施。github

代码即法律?

对智能合约理念的字面解释形成了“代码即法律” ( “Code is Law” ) 的范式理解,意思是那些智能合约具备约束力,并被诠释为合法文件。全部意识到没法建立彻底无错代码的软件工程师在想到计算机程序具备合法约束力的时候,手心都会捏一把汗。这里存在不少明显的问题:算法

  1. 代码有漏洞。写无bug代码是一件很是困难的事情,即便作好了全部可能的预防措施,在至关复杂的软件中也总会存在乎想不到的执行路径或者可能的漏洞。编程

  2. 法律合约也须要解释与仲裁。建立毫无漏洞法律合约是很是困难的。在任何大型合约中,拼写错误可能出现,一些条款须要解释和仲裁。这就是为何在出现争议时咱们须要法庭。若是在某个法律合约中,40页中有39页标定的销售价格为100美金,而在某一页上的价格中多了个“0”,法院将以“合约精神”为准进行裁定。而计算机只会执行写好的条款,区块链的不变性增长了这个问题,由于合约没法轻易修改。安全

  3. 软件工程师不是法律专家,反之亦然。起草一份好的合约须要与编程不一样的技能,一名可以编写很是完善计算机程序的人员不必定会写法律合约。bash

两起著名的利用智能合约漏洞的事件

The DAO 攻击

这件事不少人都早已谈了许多,咱们就不在这重复了。您能够在这里找到关于攻击和蔼后的完整概述。并发

简单来讲,在 2016 年 6 月,一名黑客企图转移一大笔众筹资金 (350 万个 ETH, 占当时ETH总数的15%)至他本身的子合约,这笔资金被锁定在该子合约中 28 天,于是你们要与时间竞赛寻找解决方案。app

这事件要注意的重点在于,攻击者是经过使合约以意料以外的方式运行而发起的攻击。这个事件中,攻击者利用了 可重入漏洞( Reentrancy Vulnerabilities )。咱们将会在这篇文章中对可重入进行深刻讨论。ide

黑客攻击Parity事件

事实上,这是第二次 Parity 所提供的的多重签名钱包合约受到黑客入侵了。众多创业公司使用的多重签名钱包的逻辑大多经过库合约实现。每一个钱包都包含一个轻量级的客户端合约,链接到这个单点故障(译注:即上述库合约)。

-Parity 多重签名钱包结构-

这个库合约中存在一个重大的漏洞,问题在于其中一个初始化函数只能被调用一次。

2017年11月,一名男子经过实施合约初始化,将本身变成了合约全部者。这容许他调用全部者才能调用的 函数,他利用这一特权调用了如下的函数:

// kills the contract sending everything to `_to`.
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
    suicide(_to);
}
复制代码

这至关于一个能使合约无效的自毁按钮。调用这个函数会致使客户合约的全部资金有可能被永久冻结。

直到撰写本文之时,该事件究竟是黑客形成的故意攻击仍是意外仍然是个谜,尽管肇事者声称这是意外行为。

两次攻击都说明了即便是由以太坊生态系统的大佬来写相对简单的合约,也容易出现基本的错误,带来严重的后果。

已知的漏洞与陷阱

私钥外泄

使用不安全的私钥纯粹是用户的失误,而不是一个漏洞。然而,尽管如此,咱们仍是要指出来,由于私钥外泄老是意外地发生,有些玩家专门从不安全的地址中窃取资金。

把开发地址(如 Ganache/TestPRC 使用的地址)用于生产的事情常常发生。这些地址是由公开的私钥生成。一些用户甚至无心识地把这些私钥导入到钱包软件,由于他们使用 Ganache 的种子词(seed words)生成了相同的私钥。攻击者则监视着 TestPRC 地址,无论多少数量的以太币只要转移到以太坊主链上的 TestPRC 地址都会马上消失(在2个区块内)。一项有趣的研究对这一十分有利可图的“清扫(sweeping)”活动进行了调查,并发现每一个清扫者(sweeper)的帐户都设法累积了高达2300万美圆的资金。

可重入与竞态条件(Race Conditions)

若是一个函数在执行完成前被调用了数次,发生意料不到的行为时,可重入漏洞就可能出现。

让咱们看看下面这个函数,它能够用于从合约中提取调用者的总余额:

mapping (address => uint) private balances;
function payOut () {
    require(msg.sender.call.value(balances[msg.sender])()); 
    balances[msg.sender] = 0;
}
复制代码

调用 call.value() 会致使合约外部代码的执行。若调用者是另外一份合约,这就意味着合约回退措施的执行。这可能会在余额调整为 0 以前再次调用 payOut(), 从而得到比可用资金更多的资金。

这种状况下的解决方法就是使用替代函数 send() 或 transfer() ,后二者函数能为基础运做提供足够的 Gas,而想要再次调用 p*ayOut()* 时 Gas 就不足了。

若合约含有两个共享状态的函数,那么不须要重复调用函数也可能会发生类似的竞态条件(Race Conditions)。所以,最好的作法是在转帐前更改状态,即转移资金前,在上述代码中把余额设为 0。

The DAO 攻击利用了该漏洞的一种变体。

下/上溢

余额通常用无符号的整数表示,在 Solidity 语言中一般为 256 位数字。当无符号整数上溢(overflow)或下溢(underflow)时,其数值会发生明显变化。让咱们看看下面一个比较常见的下溢例子 (为了清晰一点我把数字缩短了):

0x0003
- 0x0004
--------
  0xFFFF
复制代码

这里很容易看出问题,减去一个比可用余额大的值便致使下溢。获得的余额是一个很大的数字。

还要注意的是因为舍入偏差(Rounding Errors),在整数中算术分割(Arithmetics Division)是很麻烦的。

解决方法是时刻对代码进行下溢、上溢检查。使用安全数字库能协助检查,好比 OpenZeppelin 的 SafeMath

交易顺序假设

交易进入未确认的交易池,并可能被矿工无序地包含在区块中,这取决于矿工的交易选择标准,有多是一些旨在从交易费中获取最大收益的算法,但也能够是其它任何标准。所以,打包在区块中的交易顺序与交易生成的顺序彻底不一样。所以,合约代码没法对交易顺序做出任何假设。

由于交易在记忆池(Mempool)是可见的,其执行是可预测的,因此除了合约执行出现意外结果的状况,还有一个可能的攻击面。交易打包中可能出现的一个问题就是,延迟某个交易可能被流氓矿工用做我的利益。事实上,可以在交易执行前意识到某些交易(的存在)对任何人来讲都是有利的,而不只仅是矿工。

对时间戳的依赖

时间戳(Timestamps)是由矿工生成。所以,合约不该该让关键操做依赖于区块时间戳,例如把时间戳用做一个生成随机数的种子。Consensys 在他们的指导手册中给出了“12分钟规定”,代表若是你依赖时间戳的代码可以处理 12 分钟的偏差,那么使用block.timestamp 是安全的。

短地址攻击

Golem team 揭露了一个有趣的攻击,详情请看这里。该漏洞影响了 ERC20 代币传输和一些相似的合约,该漏洞的问题在于交易字节代码能够是任意大小,而以太坊虚拟机(Ethereum virtual machine,简称EVM)会在其尾部缺失的字节填充0。

实施该攻击须要找到一个以十六进制(hex)形式表示且结尾为若干个 0 的地址,并在提币请求中省略这些结尾的 0。当该合约发起一个转帐请求时,短地址被插入,其他的交易字节代码被移位。

举个例子,省略结尾的两个 0 会致使交易数据中地址以后的字节发生 1 个字节的移位。地址后面是交易数据中的参数,一般是无符号的前置 0 的 256 位整数。这些前置的 0 会移入地址字段,使地址有效并确保交易目的地是正确的。

参数字段中一个字节的移位也很容易致使提币量变为原来的256倍。在EVM用 0 填充缺失的结尾字节后,交易成功,而后转走 256 倍的金额。

所以,利用省略两个十六进制0的地址的漏洞使攻击者能够从一个余额为 1000 个代币的帐户中提取 256000 个代币,以此类推。省略 4 个结尾的 0 则是 2^16 倍。

为了不这种攻击,你的合约应该验证地址。

拒绝服务攻击(DoS Attacks)

有时经过使合约交易超过可以包含在一个区块中的最大 Gas 量,来迫使合约交易失败。在这篇拍卖合约的解读中解释了这个经典例子。迫使合约退还大量没有接受的小投标会增长 Gas 消耗量,若是能耗超过了区块 Gas 上限,那么整个交易失败。

这个问题的解决方法是避免许多交易调用可能由相同的函数调用引发的状况,尤为是若是调用次数会受到外部影响。

推荐的付款模式是让客户请求转帐,而不是一次性转帐出去,如 Solidity 官方文件所述。

缓解措施与结论

为了强调“代码即法律”这一范式理解的危害,本文咱们阐述了可能发生的漏洞以及过去攻击者是如何利用这些漏洞的例子。

最近的历史事件代表在公链上执行图灵完备的智能合约是危险的,其安全性远不足以取代传统法律系统的语言准确度与解释和仲裁空间。

但这并不意味着咱们应该抛弃智能合约。智能合约是很是有用的工具,能开发出有趣的应用程序。然而,咱们不能认为智能合约能取代具备法律约束力的合约,它只是用于自动化的补充工具。

另外,咱们应该作好预防措施去避免漏洞:

  • 使用开放的资源与社区接受的库合约的实质标准 (de facto standards),例如 Open Zeppelin’s contracts

  • 使用推荐的模式与最优操做指导手册,例如 Consensys 提供的。

  • 考虑由信誉好的供应商审核您的智能合约。

原文连接: medium.com/cryptronics…

做者: Stefan Beyer

翻译&校对: 杨哲 & Elisa

稿源:以太坊爱好者(ethfans.org/posts/ether…

相关文章
相关标签/搜索