📜 [專欄新文章] 智能合約 Storage 注意事項
✍️ Jun-You Liu
📥 歡迎投稿: https://medium.com/taipei-ethereum-meetup #徵技術分享文 #使用心得 #教學文 #medium
前言
前幾天在逛 Medium 的文章,意外看到這篇在討論如何利用 Ethereum Smart Contract Storage 實際行為跟預想的落差來攻擊。由於這個漏洞相較溢位等其他漏洞比較不會被注意到,決定記錄起來,如果大家未來在使用 Storage 時可以多注意一下。
Code
先來看看以下這個 contract:
這是一個幫助使用者 hodl Ether 的 smart contract。當使用者將 Ether 放進這個 contract 中,會被強制鎖定一段時間之後才能取出,在這段時間中使用者毋須擔心自己會受不了誘惑把錢拿去亂投資 ICO 導致血本無歸 (?)。
攻擊
問題出在 PayIn() 這個函式。當使用者想要存一些 Ether 進去,他會送出一筆存款的交易給這份 smart contract,但這筆存款交易其實不會記錄在使用者的 balance,而是會記錄到這份 smart contract owner 的 balance 中。
為什麼會這樣呢?
讓我們仔細看看 payIn() 在幹嘛,以下是他裡面在做的事:
HoldRecord newRecord;newRecord.amount += msg.value;newRecord.unlockTime = now + holdTime;balance[msg.sender] = newRecord;
問題出在第一行,在 EVM (Ethereum Virtual Machine) 中,當沒有指定 storage/memory 時,預設會使用 storage,所以 newRecord 會是 storage 的 pointer。而 newRecord 又沒有給定初始值,那麼 newRecord 就會指向 address(0),也就是這份 contract 最一開始的地方。詳細行為可以參照 Solidity 的文件。
那麼本來 newRecord 是希望指到 HoldRecord 的 struct,藉此存取
uint amount;uint unlockTime;
其實指到的是 contract 一開始的
uint ownerAmount;uint numberOfPayouts;
所以在 payIn() 的newRecord.amount += msg.value; 其實是加到 ownerAmount 中,也當然 numberOfPayouts 也被覆蓋掉了。然後 owner 就可以用 ownerWithdrawal() 來將剛剛使用者存入的錢偷走。
反攻擊
但這邊其實有個反攻擊的方法。在 payIn() 的第四行
balance[msg.sender] = newRecord;
如前面所說,newRecord 是指到 ownerAmount,所以這行其實是把 ownerAmount 指定給 msg.sender,使用者可以用一個很短的鎖定時間存一筆很小的 Ether,然後在到期後馬上使用 withdrawal() 將 owner 的所有錢提領出來。
結論
這個例子是要舉出在 smart contract 中 storage 預設行為的危險性。其實只要維持一個原則就可以避免這個問題。
養成明確定義使用 storage 還是 memory 的好習慣
一般來說,指定 storage 時就直接給初始值;而在 function 裡面需要用到的暫存器都用 memory,除非想要直接修改鏈上的值。現在 compiler 都會很聰明的提醒開發者要定義 storage 還是 memory,而當 storage pointer 沒有初始值時也會提醒開發者。
然而如果不是開發者只是使用者,其實在看 code 的時候很容易會忽略掉這部分的漏洞,所以在有更安全的選項出來前,就要多加注意了。
在此附上我的更改版,一批很純的Hodl (?)
如果覺得內容哪裡有誤,歡迎留言討論交流,然後別忘了分享給你所有很愛用預設參數的朋友 (?
智能合約 Storage 注意事項 was originally published in Taipei Ethereum Meetup on Medium, where people are continuing the conversation by highlighting and responding to this story.
👏 歡迎轉載分享鼓掌
同時也有10000部Youtube影片,追蹤數超過2,910的網紅コバにゃんチャンネル,也在其Youtube影片中提到,...