智能合约审计-时间操纵
2020年1月11日 星期六, 发表于 合肥
如果你对本文有任何的建议或者疑问, 可以在 这里给我提 Issues, 谢谢! :)
描述:以太坊智能合约中使用block.timestamp来向合约提供当前区块的时间戳,并且这个变量通常被用于计算随机数、锁定资金等。但是区块的打包时间并不是系统设定的,而是可以由矿工在一定的幅度内进行自行调整。因此,一旦时间戳使用不当,则会引起漏洞
核心问题:矿工操纵时间戳生成对自己有利的随机数,或者来解除合约的时间限制
以太坊中的时间戳合理要求
-
当前区块的时间戳一定大于上一个区块的时间戳
-
当前区块的时间戳与上一个区块时间戳之差小于900S
-
矿工可以在这个“合理”范围内任意设置时间戳
-
引入问题:矿工对于时间戳这个看似“客观”的变量有很大的控制权
漏洞合约分析
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);
}
}
}
合约讲解:开奖时间被硬编码到合约中,只有等到开奖时间到来之后才能开奖
漏洞点:矿工可以在时间戳即将到来之前,将包含该笔交易的区块时间戳稍微提前,就可以提前开奖
漏洞预防
- 在合约中使用block.timestamp时,需要充分考虑该变量可以被矿工操纵,评估矿工的操作是否对合约的安全性产生影响
- block.timestamp不仅可以被操纵,还可以被同一区块中的其他合约读取,因此不能用于产生随机数或用于改变合约中的重要状态、判断游戏胜负等
- 需要进行资金锁定等操作时,如果对于时间操纵比较敏感,建议使用区块高度、近期区块平均时间等数据来进行资金锁定,这些数据不能被矿工操纵