如何编写安全的solidify合约

迄今为止,发现的重要安全漏洞都是因为智能合约编写安全性考虑不周到引发的。所以智能合约的审查与优化是重中之重

一、常见漏洞

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

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

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

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

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

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

历史上的重大漏洞:一行代码蒸发了 ¥6,447,277,680 人民币!

2、可见性(Visibility)

简而言之就是控制好合约方法的访问权限,就像Java中的private,public等一样。

solidify合约可见性范围:

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

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

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

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

注意:你使用智能合约的所有事情,都是公共可见的,即使是局部变量和标记为private的状态变量。

3、delegatecall(): parity事件

引述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

delegatecall该方法要慎用,并且做好完备的权限控制

典型案例:parity事件
Parity多重签名钱包被盗事件之技术分析
3张图看懂Parity钱包新漏洞-上亿的安全课

4、可重入性(The DAO黑客事件)

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

简单来说,就好像银行出纳员在她把你所要求的钱全部给你之前,不会更改你的余额。“我能取出500美金吗?等一下,在那之前,我能取出500美金吗?” 等等等等。按照设计的智能合约只会在最开始检查你有500美金,一次,而且允许它们自己被打断
在这里插入图片描述
正如这里所详细描述的,修复方案就是先减少发送人的余额再进行价值转移。对于使用并行编程的人来说,另外一个解决方法就是用互斥锁,从而一起缓解各种竞争条件。

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

另一个实例:

任何合约A与合约B的交互以及任何发送以太币都会把程序控制权交给合约B。这使得合约B在交互结束之前,可以回调A。举个例子,下面的代码有bug(这只是代码片段,不是完整的合约代码):

pragma solidity ^0.4.0;
// 这个合约包含bug,请不要使用
contract Fund {
	/// 映射合约的以太币股份
	mapping(address => uint) shares;
	/// 取回你的股份
	  function withdraw( ) {
		if (msg.sender.send(shares[msg.sender]))
		shares[msg.sender] = 0;
	}
}

这个问题不是很严重,因为send也有gas限制,但是依旧暴露出一个问题:以太币交易总是伴随着代码执行,所以接收者可能是一个会执行withdraw函数的合约。这会使得它可以多次取回金额,并且可以取回该合约的所有以太币。

为了防止重入,你可以使用下面的检查影响交互模式:

pragma solidity ^0.4.11;
contract Fund {
	/// 映射合约的以太币股份
	mapping(address => uint) shares;
	/// 取回股份
	  function withdraw( ) {
		var share = shares[msg.sender];
		shares[msg.sender] = 0;
		msg.sender.transfer(share);
	}
}

注意,重入不仅对以太币交易有影响,而且可以是合约里的任何函数。另外,你必须考虑合约账户的多合约交互情况。一个被调用的合约可能会改变调用者的状态。

注:上面代码中还应该判断余额是否充足。上述两个案例都是:先减少发送人的余额再进行价值转移

The DAO的漏洞利用分析:

The DAO的漏洞利用分析
TheDAO被攻击事件考察报告
彭博社深度还原The DAO大劫案始末:过去已过去,未来仍需创造

二、其他注意的点:

2.1 sender和transfer

当发生错误时,transfer会抛出异常,sender返回false。我们一定要根据异常或者返回值来精确判断交易是否执行成功。

最佳实践是使用 “取回”模式而不是“发送”模式:即让币的接收者来主动索取他应该得到的币

2.2 如何获取发送者:msg.sender和tx.origin

msg.sender只能获取直接上级的地址,而tx.origin可以获取真正的交易发起者。所以避免使用tx.origin来进行权限控制,除非你确实搞明白了两者的区别及风险。

2.3 避免使用var

var可能会造成内存溢出,比如:

var length =300;

for(var i = 0 ; i < length ; i ++){

}

上述程序是一个死循环。因为var i = 0 ;i 默认是uint8 。所以i最大是2的8次方-1=255,255再加1,i又变为了0.

2.4 “检查生效交互”模式

对应上面的重入BUG

2.5、充分的容错及测试

三、其他参考资料:

3.1、OpenZeppelin合约安全解决方案

声明:OpenZeppelin可以简化我们的开发。因为它内置了很多东西,包括安全等方面。

现在使用OpenZeppelin的SafeMath库已经成为了一个标准。

openzeppelin官网

SafeMath库地址

OpenZeppelin中实现了安全的ERC20标准的代币 。我们可以多多借鉴参考

3.2、solidify安全考虑【官方】

官方原文

中文翻译(翻译的不太好)

安全考量——Solidity中文文档(5)

3.3、其他参考资料

如何使用Solidity编写安全的智能合约代码?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐崇拜234

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值