不少以太坊的智能合约控制着有实际价值的数字资产。所以,保证合约没有安全漏洞是十分重要的事情。这几期为你们带来一篇 2017 年对以太坊合约攻击调研的文献,来帮助你们避免以太坊智能合约设计中的一些可能致使安全性问题的弱点。在这里,你也能够看到,致使以太坊分叉的著名事件 The DAO 攻击,其原理是什么。安全
这篇文献分两部分,第一部分介绍了一些若是对 Solidity 语言和智能合约不当使用会致使问题的弱点;第二部分则用一些实例展现了这些弱点可能会致使怎样的问题。微信
咱们今天还推送了另外一篇文章做为背景资料,为不熟悉智能合约和 Solidity 语言的读者介绍一些背景内容。less
不当使用会致使问题的点
合约内函数调用
在使用 Solidity 编写智能合约时,能够调用其余合约中的函数。假设 Alice 合约里有一个 ping(uint) 函数, c 是一个 Alice 合约在以太坊上的地址。若是其余合约(或 Alice 合约自身)想要以参数 42 调用 ping 函数,有三种方式:ide
第一种函数
call 调用:经过合约地址,合约函数,函数签名和调用参数进行调用。若是被调用函数中有修改合约变量的代码,将修改被调用合约中相应的变量。性能
第二种区块链
delegatecall 与 call 相似,区别是 delegatecall 执行时,仅仅使用被调用函数的代码,而代码中若是涉及到合约变量的修改,则是修改调用者合约中的变量。若是被调用的函数中有 d.send(amount)的指令,表示向地址 d 转必定数额的以太币,在 call 模式下这笔钱从被调用合约的余额中转出。在 delegatecall 模式下将从调用者合约的余额中转出。ui
所以, delegatecall 是更危险的命令,若是这一命令加载的函数代码是合约编写者不可控的,可能会致使合约的钱被转走或合约被销毁等严重后果。spa
第三种设计
这第三种调用方式在论文中被称为直接调用 (direct call). 它先在合约里声明了 Alice 合约须要调用的函数,而后调用它。这种方式与以上两种方式在异常处理上会有区别。
须要注意的是,以上三种方式若是将函数名或者参数类型设置错误,则会调用回退函数 (fallback function). 若是是由于笔误打错了内容,可能会触发本不应执行的回退函数中的代码。
Gasless Send
在 Solidity 中,若是变量 rec 的类型为 address, 那么 rec.send(amount) 表示由合约向地址 rec 转帐数额为 amount 的 wei. (10^18 wei = 1 ether ) 在这个执行的过程当中,还会触发地址 rec 的回退函数。若是回退函数执行过程当中消耗的 gas 大于 2300,则会触发一个异常,致使转帐失败。
异常处理
在背景介绍中咱们提到过,使用 Solidity 执行智能合约时会抛出异常,可是不一样的合约内函数调用方式对异常(exception)的处理方式不同。
若是合约执行过程当中没有函数调用,或者只有 direct call 直接调用,那么当触发一个异常的时候,视为合约执行失败,直接中止合约的执行,回滚执行过程当中的转帐和对合约变量的修改等操做,并扣除所有的交易费用。
若是经过 call, delegatecall 或 send 调用其余合约函数,在执行期间触发的异常不会影响原有函数。也就是说,若是在执行 send 的触发的回退函数过程当中,若是 gas 不足引发了异常,转帐会失败,可是原有合约会被成功地执行。
若是对这一点缺少足够的理解,错误地认为合约执行成功意味着 call 调用也必定成功,错误地认为没有触发异常就意味着 ether 转帐成功,就可能致使合约有安全性问题。正确的作法应当是经过函数调用返回的结果判断其执行是否成功。而一些研究代表有 28% 的合约没有检查返回结果。(固然,这不意味着必定有安全问题)
重入问题
Solidity 中回调函数的机制,可能会让合约调用其余函数后,被调用的函数又调用了调用者合约的函数,形成循环,下面是一个例子
假设区块链上已经以下的合约 Bob,若是 sent 变量为 false, 就向给定地址发送一笔钱。
而 Mallory 是攻击者恶意构造的合约,代码以下所示。
Bob 合约设计的本意是,若是 sent 变量为 false, 就向给定地址发送一笔钱。然而,当这笔钱发往攻击者合约时,会触发攻击者合约的回退函数,回退函数再次调用 ping 函数,如此无限循环,直到交易费耗尽或调用深度达到上限 1024 次触发异常。但以前提到了,对于 call 调用的函数在执行过程当中触发的异常,不会影响原来的函数的成功执行。也就是说,除了最后一步转帐会失败,以前的转帐都会成功。
几种攻击
接下来,咱们介绍几种利用上面提到弱点的攻击例子。
DAO 攻击
DAO 攻击是以太坊历史上最著名的攻击,盗走了价值 6000 万美圆的以太币。以太坊社区经过强行回滚硬分叉了以太坊,致使了以太坊和以太经典两条分叉链并存的局面。
下面是一个简化版的 DAO 智能合约,但足以描述 DAO 合约的漏洞。
这个合约的功能很简单,任何人能够向指定地址捐献以太币,受捐赠人能够提走本身受捐赠的币。
而攻击者经过如下的合约,就能够大量转走合约中的币。
其原理与上文所说的重入问题彻底同样, SimpleDAO 合约的 withdraw 函数执行时向攻击者合约转帐,转帐会触发攻击者合约的回退函数,攻击者合约的回退函数会从新调用 SimpleDAO 合约的 withdraw 函数,造成一个循环。当循环由于各类缘由结束的时候,除了最后一步,以前的执行都不会失败。攻击者转出了大量的钱。
另外,这个合约没有考虑整数溢出问题,所以有以下攻击成本更低的方案
在这个合约中,攻击者设计了一个函数 attack, 当这个函数被执行的时候,攻击合约先给本身捐赠 1 wei, 而后把这 1 wei 取出来。在取钱的时候,会触发攻击者合约的回退函数。与以前的攻击不一样,此次咱们只利用重入问题 1 次,也就是 withdraw 函数被执行了两遍。在 withdraw 第二次向攻击者转帐之后,攻击者再也不调用 withdraw.
因而 withdraw 函数中的转帐操做 msg.sender.call.value(amount)() 发生了2次,天然地,它的下一行也会被调用 2 次。这两次被调用将 credit[攻击者地址] 变成了 -1 wei, 会被虚拟机解读为 2^256-1 wei. 这时,攻击者能够从中取出几乎无限多的钱出来。
特别的是,即便 withdraw 函数在转帐后检查 send 执行是否成功,也只能防范第一种攻击。
以太王座
考虑下面一个游戏合约,在游戏中,你们将竞争一个王座。后来者能够经过向王座上的人支付一笔钱来取而代之,每一轮取得王座须要的钱都要比上一轮高。最后取得王座的人有额外的收益。(没有在合约中体现。)
这个合约看上去没什么问题。事实上,他人在向王座上的人(地址)支付费用的时候,会触发那个地址(若是是一个合约)的回退函数。若是王座上合约地址的回退函数须要的交易费太高,会触发 gasless send 的问题,就会致使转帐失败。但后续变动王座拥有者的代码还会照常执行,新来者能够毫无成本地得到王座。
修改这一问题的思路看上去很简单,只要将转帐的代码 king.send(compensation) 变成 if(!king.call.value(compensation)())throw; 来判断一下转帐是否成功就能够了。然而这会致使另外一个问题。王座上的地址(合约)将本身的回退函数设定成必定会触发异常,例如 function(){throw;},就没有人有能力将他从王座上赶下去了,由于全部转帐的结果都会失败。
以上就是这一期的内容,在接下来的文章中,咱们将会介绍文献中提到的其余的 Solidity 的弱点与可能致使的问题。
参考文献:
[1] Atzei, Nicola, Massimo Bartoletti, and Tiziana Cimoli. "A survey of attacks on ethereum smart contracts (sok)." Principles of Security and Trust. Springer, Berlin, Heidelberg, 2017. 164-186.
Conflux 是致力于打造下一代高性能的 DAPP 公链平台
欢迎关注咱们的微信公众号:Conflux中文社区(Conflux-Chain)
添加微信群管理员 Confluxgroup 回复“加群”加入 Conflux官方交流群