solidity漏洞類型學習筆記(一)
以下代碼內容皆參考於RICKGRAY師傅之前的文章《以太坊智能合約安全入門瞭解一下》,在此記錄我在復現中發現的一些問題和學習記錄。
Reentrancy - 重入
首先我們先參考代碼實現一個類似公共錢包的代碼,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
balances定義了一個下標爲[address]的公共錢包,deposit函數向錢包中調用者的位置存入相應的value值,withdraw函數檢查提幣賬戶的餘額與該合約資產是否大於參數amount,之後向to地址發送相應Ether。
部署成功後我們調用deposit函數向錢包中存入25ether,可在balanceof處輸入"0xca3...a733c"的地址查看錢包中該地址的餘額是否爲25*10^18wei。隨後我們將這個錢包中的餘額轉給另一個外部用戶:
此時的調用者依然是"0xca3...",拷貝第二個外部賬戶的地址"0x147..."。輸入參數"0x147...",25調用withdraw()函數,成功轉賬。
這裏存在着一個問題:當外部賬戶或其他合約向一個合約地址發送ether時,會執行該合約的fallback函數(當調用合約時沒有匹配到函數,也會調用沒有名字的fallback函數——The DAO)。且call.value()會將所有可用Gas給予外部調用(fallback函數),若在fallback函數中再調用withdraw函數,則會導致遞歸問題。攻擊者可以部署一個惡意遞歸的合約將公共錢包這個合約賬戶裏的Ether全部提出來。【1、call.value()提供了足夠的Gas 2、資產的修改在轉幣之後】
Solidity 中 <address>.transfer(),<address>.send() 和 <address>.gas().call.vale()() 都可以用於向某一地址發送 ether,他們的區別在於:
<address>.transfer()
* 當發送失敗時會 throw; 回滾狀態
* 只會傳遞 2300 Gas 供調用,防止重入(reentrancy)
<address>.send()
* 當發送失敗時會返回 false 布爾值
* 只會傳遞 2300 Gas 供調用,防止重入(reentrancy)
<address>.gas().call.value()()
* 當發送失敗時會返回 false 布爾值
* 傳遞所有可用 Gas 進行調用(可通過 gas(gas_value) 進行限制),不能有效防止重入(reentrancy)
以下是rickgray師傅實現的攻擊代碼,有小修改,攻擊流程在他的博客中也有GIF。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
|
可能是Remix的原因我在一開始復現時就是不成功,後來查原因之後在輸入參數處加上引號即可。
The DAO:
第一處紅線向攻擊者賬戶轉錢,第二處withdrawRewardFor函數:
在payout中調用攻擊者_recipient,但沒有指定具體函數則調用fallback函數,在fallback函數中會再次調用splitDAO函數,實現惡意遞歸。在方框中的修改餘額代碼執行之前,就完成了偷錢操作。