智慧合約安全審計之路-時間操縱漏洞

描述:以太坊智慧合約中使用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);          }      }  }

合約講解:開獎時間被硬編碼到合約中,只有等到開獎時間到來之後才能開獎

漏洞點:礦工可以在時間戳即將到來之前,將包含該筆交易的區塊時間戳稍微提前,就可以提前開獎

漏洞預防

  1. 在合約中使用block.timestamp時,需要充分考慮該變數可以被礦工操縱,評估礦工的操作是否對合約的安全性產生影響
  2. block.timestamp不僅可以被操縱,還可以被同一區塊中的其他合約讀取,因此不能用於產生隨機數或用於改變合約中的重要狀態、判斷遊戲勝負等
  3. 需要進行資金鎖定等操作時,如果對於時間操縱比較敏感,建議使用區塊高度、近期區塊平均時間等數據來進行資金鎖定,這些數據不能被礦工操縱