My Avatar

毕竟话少

业精于勤荒于嬉,行成于思毁于随!

智能合约审计-时间操纵

2020年1月11日 星期六, 发表于 合肥

如果你对本文有任何的建议或者疑问, 可以在 这里给我提 Issues, 谢谢! :)

描述:以太坊智能合约中使用block.timestamp来向合约提供当前区块的时间戳,并且这个变量通常被用于计算随机数、锁定资金等。但是区块的打包时间并不是系统设定的,而是可以由矿工在一定的幅度内进行自行调整。因此,一旦时间戳使用不当,则会引起漏洞

核心问题:矿工操纵时间戳生成对自己有利的随机数,或者来解除合约的时间限制

以太坊中的时间戳合理要求

漏洞合约分析

TimeGame1合约分析

pragma solidity ^0.4.24;

contract TimeGame1{
    uint public lastBlockTime;
    
    function lucky() public payable{
        require(msg.value == 100 wei);
        require(lastBlockTime != block.timestamp); //block.timestamp获取当前区块的时间戳
        lastBlockTime = block.timestamp;
        if(lastBlockTime % 10 == 5){
            msg.sender.transfer(address(this).balance);
        }
    }
}

合约讲解:合约通过交易发送所在区块时间戳来决定是否获奖,每个区块中只允许第一笔交易获奖,若区块时间戳的十进制表示最低位是5,交易发送者即可获奖。

漏洞点:由于矿工有个0~900s的任意设置时间戳的权限,导致矿工可以非常轻易的来设置满足交易的时间戳。普通用户可以自己写一个攻击合约来调用lucky(),也是可以自由设置满足交易的时间戳

TimeGame2合约分析

pragma solidity ^0.4.24;

contract TimeGame2{
    bool public neverPlayed=true;
    
    function check(uint answer) public returns(bool){
        return true;
    }
    
    function play() public {
	require(now > 1577808000 && neverPlayed == true); //now即为block.timestamp的另一种写法
	if (check(233) == true){
    	neverPlayed = false;
    	msg.sender.transfer(1500 ether);   
    	}
    }
}

合约讲解:开奖时间被硬编码到合约中,只有等到开奖时间到来之后才能开奖

漏洞点:矿工可以在时间戳即将到来之前,将包含该笔交易的区块时间戳稍微提前,就可以提前开奖

漏洞预防

  1. 在合约中使用block.timestamp时,需要充分考虑该变量可以被矿工操纵,评估矿工的操作是否对合约的安全性产生影响
  2. block.timestamp不仅可以被操纵,还可以被同一区块中的其他合约读取,因此不能用于产生随机数或用于改变合约中的重要状态、判断游戏胜负等
  3. 需要进行资金锁定等操作时,如果对于时间操纵比较敏感,建议使用区块高度、近期区块平均时间等数据来进行资金锁定,这些数据不能被矿工操纵