????????智能合约中安全漏洞的例子:The DAO (2016年)、美链(2018年04月)。
1.什么是美链? ? ? ??
????????美链(Beauty Chain)是一个部署在以太坊上的智能合约,有自己的代币BEC。IPO,Initial Public Offering(首次公开募股);ICO,Initial Coin Offering(首次代币发行)。这些发行的代币没有自己的区块链,而是以智能合约的形式运行在以太坊的EVM平台上。发行代币的智能合约对应的是以太坊状态树中的一个节点。 这个节点有他自己的账户余额,相当于这个智能合约一共有多少个以太币,也就是这个发行代币的智能合约的总资产是多少个以太币,然后在这个合约里每个账户上有多少个代币是作为存储树的一个变量存储在智能合约的账户里的。
????????代币的发行、转账、销毁都是通过调用智能合约中的函数来实现的。跟以太坊上的以太币不太一样,它不需要通过挖矿来维护一个底层的基础链,像以太坊上每个账户有多少以太币是直接保存在状态树上的变量。以太坊中两个账户转账是通过发布一个交易到区块链上,这个交易会打包到将要发布的区块里,而在代币中如果想要转账的话,实际上是智能合约里两个账户之间转账,通过调用智能合约的函数就可以完成。
? ? ? ? 每个代币可以制定自己的发行规则,如某个代币可以是1ETH=100代币,如果从外部账户给智能合约发送1ETH,智能合约就可以给相应的代币账户发送100代币。每个代币账户上有多少代币的信息是维护在发行这个智能合约的存储树上的。
? ? ????以太坊平台的出现,为各种代币的发展提供了很多方便。 ERC 20(Ethereum Request for Comments)是以太坊上发行代币的一个标准,规范了所有发行代币的合约应该实现的功能和遵循的接口。美链中有一个叫batchTransfer的函数, 它的功能是向多个接收者发送代币,然后把这些代币从调用者的帐户上扣除。
2.batchTransfer的实现
function batchTransfer(address[] _receivers,uint256 _value) public whenMotPaused returns (bool) {
uint cnt = _receivers.length;
uint256 amount = uint256(cnt) *_value;
require(cnt > 0 && cnt <= 20);
require(_value > 0 && balances[msg.sender] >= amount);
balances[msg.sender] = balances[msg.sender].sub(amount);
for (uint i = 0; i < cnt; i++) {
balances[_receivers[i]] =balances[_receivers[i]].add(_value);
Transfer(msg.sender,_receivers[i],_value) ;
}
return true;
}
图1-1
?3.batchTransfer的问题
uint256 amount =uint256(cnt)*_value;
????????如果value的值很大话可能会产生溢出 ,发生溢出之后,amount会变为一个很小的值;这样的话减去的是很小的值,给每个账户增加的是很大的value,相当于系统凭空多发行了很多的代币。
4.攻击细节
图1-2
????????第0号参数是_receivers数组在参数列表中的位置,即从第64个byte开始,也就是第2号参数;第2号参数先指明数组长度为2,然后第3号参数和第4号参数表明两个接受者的地址第1号参数是给每个接收者转账的金额,通过这样的参数计算出来的amount恰好溢出为0。
5.发生攻击的实际情况
图1-3
?6.攻击结果
????????攻击在2018年4月22日发生,攻击发生后,代币的币值断崖式暴跌。?
图1-4
? ? ? ? 发生这个事件之后,发行该代币的交易所马上暂停了提币功能,防止黑客卷款潜逃,两天之后,就决定将交易回滚了。好在这个代币本来就不是很热门,主要是这个交易所在交易,所以没有产生过于严重的后果,远远没有The DAO的盗币事件影响大。
7.反思?
????????在进行数学运算的时候,一定要考虑到溢出的可能性。Solidity实际上有一个检测溢出的库——SafeMath库,里面提供的操作运算都会自动检测有没有溢出。SafeMath库里对乘法的运算大致如下:
library SafeNath {
//@dev Multiplies two numbers, throws on overflow.
function mul(uint256 a,uint256 b) internal pure returns (uint256 c) {
if (a == 0) {
return 0;
}
c = a * b;
assert(c / a == b);
return c;
}
图1-5
? ? ? ? ?如果出现溢出的话,这里的assert就不会成立,会抛出异常。由于Solidity里面都是256位的整数,所以这里不会存在精度损失而导致的误差。而且观察batchTransfer的代码可以发现,代码中的减法调用的是sub库,加法调用的是add库,实际上加法和减法都有溢出检查,只有乘法不小心,酿成悲剧。