Ethernaut – level 4: Telephone

(https://ethernaut.openzeppelin.com/level/0x0b6F6CE4BCfB70525A31454292017F640C10c768)

這一關的說明很簡潔 : 請取得Contract的Ownership。

於是我們一樣觀察程式碼,其中除了contractor()會去設定owner以外,剩下以下的函數也可以變動owner:

function changeOwner(address _owner) public {
    if (tx.origin != msg.sender) {
      owner = _owner;
    }
  }

第一眼看到,似乎只要呼叫contract.changeOwner(player) 就可以達成過關目標,但是,程式有一個if, 要求tx.origin != msg.sender,因此,我們來看看 tx.origin 與msg.sender各自的定義:

msg.sender: 訊息傳送者(當前呼叫的address,可以是User 錢包或是 Contract address)

tx.origin: 交易傳送者(整個呼叫鏈的最開始傳送者,只能是User錢包address)

例如:當處於 User 呼叫 Contract A ,然後Contract A 呼叫 Contract B 這樣的呼叫鏈下,在Contract B的msg.sender是Contract A,tx.origin是User!

Source:https://0xsage.medium.com/

所以解析起來,要tx.origin不等於msg.sender就需要類似Scenario 3的情況,要創建一個Contract A來呼叫 changeOwner,才能達成上述的條件。所以我們就在Remix上寫以下的contract並部署然後執行囉。

創建ContractA 來呼叫changeOwner()
Remix上部署完成後呼叫

最後,回到Ethernaut頁面的developer tool->console,輸入await contract.owner(),檢查一下owner是否已經變成你了,Okay,Submit,level cleared!

Key Takeaways:

使用tx.origin作為授權判斷時要小心,因為有心人士A可以寫一個Contract B讓無辜受害者C去呼叫,而你的Contract D 若以tx.origin作為授權判斷,會以為是無辜受害者C 的正常呼叫,這時Contract B中可以寫一些例如轉帳給A 的功能,則可能C的Ether就被轉到A了;但若以msg.sender來做授權判斷則可以避免上面這樣的情況。