智慧合約安全審計之路-時間操縱漏洞
- 2020 年 3 月 8 日
- 筆記
描述:以太坊智慧合約中使用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不僅可以被操縱,還可以被同一區塊中的其他合約讀取,因此不能用於產生隨機數或用於改變合約中的重要狀態、判斷遊戲勝負等
- 需要進行資金鎖定等操作時,如果對於時間操縱比較敏感,建議使用區塊高度、近期區塊平均時間等數據來進行資金鎖定,這些數據不能被礦工操縱
