智慧合約安全
安全最佳實踐
最小化/簡單化
程式碼重用
程式碼質量
因為你處在航天工程那樣或其他類似的零容錯的工程領域之中
可讀性和可審計性
智慧合約是公開的,任何人都可以獲得其位元組碼並進行反向工程
因此智慧合約很契合在開源社群協作開發
測試覆蓋率
儘可能測試所有情況
安全風險
重入
漏洞
外部的惡意合約通過函式呼叫來重新進入合約程式碼執行過程。通過使用回退函式來重入合約函式。
防範技術
- 儘可能使用內建的transfer函式向外部合約傳送以太幣。transfer函式只會提供多餘的2300gas,這些ETH只夠用來用於執行外部合約傳送ETH,沒有多餘的gas可以用來重入合約
- 所有對狀態變數的修改都在向其他合約傳送以太幣之前來執行
- 引入互斥鎖,增加一個狀態變數來在程式碼執行中鎖定合約,避免重入的呼叫
整數溢位
漏洞
比如unit8範圍是[0,255],用來儲存256時就溢位變為了0,257變為1,以此類推。類似於pwn中的整數溢位漏洞。在solidity中只有整數,因此要注意加減乘等等的溢位,包括上溢下溢。除法不會導致溢位,但除以0時會丟擲異常
防範技術
使用或構建安全的算數運算的庫合約來代替標準的算數操作,如使用OpenZeppelin的SafeMath.sol
意外的以太幣
漏洞
並不是所有的向一個合約傳ETH的動作都會呼叫合約函式來處理。兩種方法:1.合約的解構函式執行時向外部轉ETH不會引發任何函式的執行,包括回退函式。2.在合約註冊生效之前,向合約地址傳送ETH。這種手法的基礎是,合約的地址是可以被確定的,可以被預知。攻擊者向合約傳送以太幣,導致合約的初始數值非零,產生一些負面影響。
防範技術
避免依賴合約餘額的具體數值(this.balance),想要儲存一個合約的充值數額應該自定義變數在payable函式記錄數額變動。
DELEGATECALL
DELEGATECALL是執行在呼叫合約的上下文,CALL則是執行在目標合約的上下文
漏洞
可能導致非預期的程式碼執行結果
防範技術
使用DELEGATECALL要非常仔細地注意庫合約和主呼叫合約的可能的呼叫上下文
並儘可能構建無狀態的庫合約
預設的可見性
漏洞
solidity中,函式預設可見性是public,因此如果函式不指定可見性,函式就是可以被外部使用者呼叫的
防範技術
對合約中的所有函式明確指定可見性
無序錯覺
漏洞
區塊鏈是一個確定性的系統,任何來自於區塊鏈系統內部的隨機數都是偽隨機的。這意味著可以被人為控制。包括雜湊值、時間戳、區塊號或者gas上限。一個例子是,如果使用未來的區塊雜湊值判定賭局,礦工可以不釋出不利於自己的區塊,而是重新打包區塊直到得到有利的新的區塊雜湊值再發布。如果使用過去的變數更加災難,因為這些對所有人都是透明的,也就更容易被攻擊者知曉
防範技術
無序性(隨機性)必須來自區塊鏈外部,如使用RandDAO
外部合約引用
漏洞
在部署時更改外部合約引用,從而執行攻擊者自己的程式碼
防範技術
- 使用new建立引用的合約,這樣以來部署的使用者也無法在不更改程式碼的情況下替換改引用合約。一旦程式碼發生變化,合約位元組碼就發生變化,合作使用者就能發現程式碼被篡改過
- 對外部合約地址進行硬編碼。將外部合約地址設定為public,使用者就可以輕鬆檢查合約所引用的外部合約,否則則認為可能存在問題。
短地址/引數攻擊
漏洞
智慧合約中,如果傳遞的引數不符合ABI規範,EVM會自動使用0來補齊缺失的位。攻擊者故意傳一個位數少了的地址引數,讓沒有檢查正確性的合約使用0自動補齊,進而產生不良影響。
防範技術
所有外部應用在把輸入引數傳送到區塊鏈之前都應該對它們進行校驗,包括引數的順序等都同樣扮演著重要角色
未檢查的呼叫返回值
漏洞
Call和send函式會返回一個布林值來指明呼叫是否成功。即使外部呼叫失敗,執行了這些函式的那個交易也不會revert回滾
防範技術
呼叫call和send函式後檢查布林值,以分別處理成功以及失敗的場景。另外儘量使用transfer函式來執行傳輸
競爭條件/預先交易
漏洞
攻擊者監視交易池中的交易,如果交易中包含了對某個問題的答案,那麼攻擊者可以去修改或者撤銷解決者的許可權,或者把合約修改為對解決者不利的狀態。然後,攻擊者從解決者的交易中獲取資料,並用更高的gasPrice建立他們自己的交易,這樣他們的交易就會優先於原始交易被打包到區塊中。
直白點:攻擊者作弊偷了答案,並用更高的gasPrice讓自己優先交卷。產生這種攻擊的原因是交易打包按gasPrice排序,gas高者會被先打包。
防範技術
有兩種角色可以發動攻擊,使用者(通過修改交易的gasPrice)和礦工(礦工可以按任意順序隨意打包交易,但只有在挖到礦的時候才能攻擊成功)。
- 為了防範使用者的攻擊,可以將gasPrice設定為上限,這樣可以防止其他使用者提高gasPrice來競爭打包。但對礦工無效,因為礦工可以以任意順序打包交易。
- 提示-揭示策略:傳送帶有隱祕資訊的交易(通常是一個雜湊值),當交易被包含到區塊後再傳送一個交易揭示先前傳送的資料。如ENS智慧合約允許使用者先提交它們希望話費的以太幣數量,同時附帶任意數量的以太幣,然後在揭示階段,使用者可以獲得以太幣返還
- 水底傳送策略:NULL
拒絕服務
漏洞
- 基於可被外部操縱的對映或陣列的迴圈:Distribute向多個賬戶分發代幣時,攻擊者建立過多的賬戶,使該合約的gas消耗超過gasLimit
- 主人的操作:必須經過擁有特權的主任來進行某些操作才能進入下一步狀態,如果主人丟失了私鑰,這個合約就進行不了下一步了
- 基於外部呼叫來修改狀態:比如建立一個不接受轉賬的合約,而新的狀態需要先將以太幣被全部取走才能進入下一步
防範技術
- 第一個例子,合約不應該基於一個可以被外部使用者人為操作的資料結構來執行迴圈
這裡推薦使用取回模式,只能讓取款人單獨地呼叫withdraw函式來取回他們各自的代幣 - 第二、三個例子,可以使用時間解鎖機制。保證意外發生,也可在到期時自動進行下一步
區塊時間戳操縱
漏洞
礦工是可以操縱時間戳的,但是時間戳必須單項正增長,同時礦工也不能指定過遠的將來時間戳。
防範技術
時間戳不應該被用來作為無序資料或生成隨機數,或者說不能用來作為重要的狀態變動。如果確實有需要時間相關邏輯,則應該使用block.number和平均區塊時間來估算(一個區塊大約是10s)。或是簡單指定一個區塊號作為條件。
小心使用建構函式
漏洞
如果合約名稱被改動後,建構函式忘記修改,就會變成普通的可被呼叫的public函式,從而引發攻擊
防範技術
0.4.22版本的solidity編譯器引入了constructor關鍵字來制定建構函式,因此此漏洞已經不存在了
未初始化的儲存指標
漏洞
EVM是用儲存或記憶體來儲存資料的,不適當的變數初始化可能會產生有漏洞的合約
防範技術
注意為初始化的儲存變數警告
在處理複雜資料型別時嚴格使用memory或storage識別符號
努力讓行為符合預期
浮點數和精度
漏洞
舊版solidity沒有浮點型,需要特殊處理
防範技術
有時候先乘後除精度會更高
Tx.Origin驗證
Solidity中的一個全域性變數,它會回溯整個呼叫棧並返回最初發起這個呼叫或交易的賬戶地址
漏洞
如果受害者的合約A是通過tx.origin==owner來授權提取餘額的,那攻擊者可以編寫攻擊合約B,誘導受害者呼叫合約B(如在將合約B偽造成外部賬戶在社交活動中釣魚),而實際上合約B呼叫了合約A(前提是轉入了足夠的gas),從而將合約A中的餘額轉入攻擊者賬戶上
防範技術
智慧合約不應該使用tx.origin來進行驗證授權。Tx.origin的合理用法如:可以用來拒絕外部合約呼叫當前合約