您的位置: 首页 > DAPP开发 > 正文

如何保护你的智能合约:6个Solidity漏洞以及如何避开它们

时间: 2021-10-04 13:03:42 浏览: 分类: DAPP开发

智能合约是“不可变的”。一旦部署,它们的代码是不能更改的,导致无法修复任何发现的bug。

在潜在的未来里,整个组织都由智能合约代码管控,对于适当的安全性需求巨大。过去的黑客如TheDAO或去年的Parity黑客(7月、11月)提高了开发者们的警惕,可我们还有很长的路要走。

“这是黑客的迪士尼乐园”

我们将在这篇文章中聊聊一些著名的安全性陷阱和它们的缓解措施。


1629116997129175.jpg

1. 溢出(Overflows)和下溢(Underflows)

溢出,就是当一个数字增加到它的最大值以上。Solidity可以处理多达256位的数字(高达2²⁵⁶-1),所以递增1会得0。

 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ 0x000000000000000000000000000000000001
----------------------------------------
= 0x000000000000000000000000000000000000

达到最大读数后,里程表或行程表从零开始重新计算,成为里程表翻转。

同样,在相反的情况下,当数字无符号时,递减会使数字下溢,从而产生最大可能值。

 0x000000000000000000000000000000000000
- 0x000000000000000000000000000000000001
----------------------------------------
= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

两种情况都很危险,但下溢情况更可能发生,例如在代币持有者具有X个代币但试图花费X + 1的情况下。如果代码不检查这种情况的化,攻击者可能最终会被允许花费比他拥有的更多的代币,并且获得最大余额

缓解措施:现在使用OpenZeppelin的SafeMath库已经成为了一个标准。



2. 可视性(Visibility)和Delegatecall

对去年7月发生的事有印象的人来说,这个bug就很眼熟,毕竟Parity钱包被黑导致用户损失了3千万美金。


Solidity的可视性修改功能和它们的区别

公共(Public)函数可以被任何人调用(被合约里的函数、继承合约里的函数、或者外面的用户)。

外部(External)函数只能从外部访问,意味着它们不能被合约里的其他函数调用。下面的要点不能编译,cannotBeCalled的外部可视性不允许它被合约里的函数调用(但它可以被别的合约调用)。

外部函数用起来更便宜因为它使用calldata操作码,而公共函数需要把所有参数复制到内存中,详情请看这里

私有(Private)和内部(Internal)函数更简单:private是只能在该合约里使用,而internal提供了一个更宽松的限制,它允许从母合约处继承的子合约使用那个函数。

那就是说,除了需要外部交互的时候,设置你的函数为private或者internal。


Delegatecall

引述solidity文件

“Delegatecall和信息调用,除了目标地址的代码要在请求调用的合约的上下文中执行之外,是完全一样的。msg.sender和msg.value不改变他们的值。 这意味着合约可以在运行是动态加载来自不同地址的代码。存储、当前地址和余额仍都取决于请求调用的合约,只有代码来自被调用合约。”

这个低级函数非常有用,因为它是实现库和模块化代码的中坚力量。 然而,它打开了漏洞的大门,因为你的合同基本上允许任何人以他们的状态做任何他们想做的。

在下面的例子中,一个攻击者可以调用合约Delegate的公共函数pwn,而且因为这个调用发生在Delegation的上下文中,他们可以对这个合约宣布主权。

Parity黑客涉及两个不安全的可见性修改功能组合,以及用任意数据滥用delegate调用。易受攻击的合约的函数实现了delegatecall,并且一个来自其他合约,能修改主权的函数被设成公共的。这使得攻击者可以设计msg.data字段来调用易受攻击的函数。

至于msg.data字段里会包含什么,那会是你想要调用函数的签名。这里的签名指的是函数原型的sha3(keccak256的别名)散列的前8个字节。

In this case:
web3.sha3("pwn()").slice(0,10) --> 0xdd365b8b
If the function takes an argument, pwn(uint256 x):
web3.sha3("pwn(uint256)").slice(0,10) --> 0x35f4581b



3. 可重入性(The DAO黑客事件)

Solidity的call函数当被带着value调用时,会发送所有它收到的gas。在下面的代码片段中,调用是在实际减少发件人的余额之前完成的。一个在TheDAO黑客事件发生时reddit上的评论很好地解释了这个漏洞:

简单来说,就好像银行出纳员在她把你所要求的钱全部给你之前,不会更改你的余额。“我能取出500美金吗?等一下,在那之前,我能取出500美金吗?” 等等等等。按照设计的智能合约只会在最开始检查你有500美金,一次,而且允许它们自己被打断。

正如这里所详细描述的,修复方案就是先减少发送人的余额再进行价值转移。对于使用并行编程的人来说,另外一个解决方法就是用互斥锁,从而一起缓解各种竞争条件。

目前来说,使用require(msg.sender.transfer(_value))是处理这些情况的最好的方法。


来源: https://dapp.skpseo.com