본문 바로가기
Block Chain/Hack Series

[Ethernaut Challenge] Fallout 문제풀이

by 개발이 체질인 나그네 2023. 1. 6.
반응형

 

안녕하세요. 스마트컨트렉트 개발자 개발이 체질의 최원혁입니다.

오늘은 The Ethernaut 퀴즈 풀이 세 번째 "Fallout" 문제를 풀어보겠습니다.

 

🔎 이번 "Fallout"의 문제해석은 마지막 목차 (5)[문제해석]에서 다루겠습니다.

(1) 베포 및 준비

먼저 페이지 하단에 [Get new instance]버튼을 눌러 해당 문제의 컨트렉트를 베포 합니다.

 

베포가 끝나면 F12(Chrom기준)을 눌러서 개발자모드를 실행시켜 주세요. 베포 된 컨트렉트의 주소를 확인할 수 있습니다.

 

(1) Ethernaut Fallout Code
(2) Remix

  • (1)사진처럼 Ethernaut에서 제공한 문제의 코드를 복사해서 (2)사진의 초록색 부분처럼 Remix에 붙여 넣습니다.
  • (2)사진의 빨간색 부분처럼 환경설정(ENVIRONMENT)을 메타마스크로 바꿔줍니다.
  • 베포 된 컨트렉트의 주소를 복사하여 (2) 사진의 노란색 부분에 붙여 넣고 [At Address] 버튼을 눌러줍니다.

  • Fallout 문제는 Solidity 0.6.0 이하 버전에 최적화되어 있으며, import 돼있는 openzeppelin 라이브러리 더 이상 지원하지 않는 라이브러리입니다. 기존의 코드를 0.8.0 버전 최적화를 위해 위 사진처럼 일부분만 바꿔주거나, 아래 창을 넓혀 해당 코드를 다시 붙혀 넣어주세요.

> 0.8.0 최적화된 전체 코드(더보기)

더보기
더보기
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/math/SafeMath.sol";

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


  /* constructor */
  function Fal1out() public payable {
    owner = payable(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 {
    payable(msg.sender).transfer(address(this).balance);
  }

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

 

Remix의 Deployed Contracts 부분을 보면 Ethernaut에서 베포 한 컨트렉트를 호출할 수 있습니다.

준비는 끝났습니다. 지금부터 The Ethernaut의 "Fallout" 문제를 풀어보겠습니다.

 


(2) 문제 설명

Claim ownership of the contract below to complete this level.
컨트렉트의 오더쉽을 가져오고 이번 레벨을 완료하세요.

이번 문제는 딱 한 줄이네요. 컨트렉트의 owner을 문제를 푸는 지갑주소로 바꾸면 끝입니다.

 Things that might help
어떤 도움을 주는지 생각해 보세요!

이번 문제는 어떤 도움을 주고자 하는지, 숨겨진 의미를 찾아볼 필요가 있어 보입니다.

🔎 이번 "Fallout"에서 설명하고자 하는 내용은 마지막 목차 [문제해석]에서 다루겠습니다.

 


(3) 문제풀이

1.  Claim ownership of the contract below to complete this level.



현재 컨트렉트의 주인(owner)은 제로주소입니다.

제가 문제를 푸는 동안 사용된 지갑주소는 0x5b6a6...4FA6 이니, owner의 값을 제 지갑주소로 바꾸면 문제풀이는 끝일 것 같네요.

 

 

owner에 지갑주소를 저장하는 함수는 function Fal1out() 하나뿐이니, 해당 함수를 실행시켜 보겠습니다.

트랜잭션이 성공한 후, owner을 보면 본인의 지갑주소로 owner가 바뀐 걸 확인할 수 있습니다. 

 


(4) 문제 제출

[Submit instance] 버튼을 눌러서 문제풀이가 끝난 컨트렉트를 제출합니다. 메타마스크에서 트랜잭션이 하나 요청이 오면 전송합니다.

문제 제출을 위한 트랜잭션이 끝나면 결과는 개발자모드에서 확인할 수 있습니다.

Well done, You have completed this level!!!

F12를 눌러 콘솔로그를 보면 Fallout 문제를 완료했다고 나오네요. 풀이 끝!

 


(5) 문제해석

이번 문제를 풀기 위해 실행했던 function Fal1out() 위에 주석을 보면 /* constructor */(=생성자)가 적혀 있는 걸 볼 수 있습니다.

Solidity 0.6.0 이하 버전에서 생성자는 Contract의 이름같은 함수명을 사용하면 컨트렉트 최초 베포 시 실행되는 생성자 함수로 사용했습니다. 지금의 Solidity 버전의 constructor과 같은 기능이지만, 다른 문법으로 사용했습니다.

 

Solidity 0.6.0 공식문서

> Solidity 0.6.0 공식 문서 constructor 링크

실제로 0.4.22 버전까지 생성자(constructor)는 solidity에 없는 함수였고, 0.4.22 버전 이후부터 생겼지만, 당시 개발자들은 0.6.0 버전까지 익숙한 함수를 사용했습니다.

 

하지만 이번 Ethernaut Fallout문제에 자세히 보면 함수의 이름이 "Fallout"이 아니라 "Fal1out"인걸 알 수 있습니다.

생성자(constructor)는 스마트컨트렉트의 최초 베포 시, 한번 실행시킬 수 있는 함수입니다. 0.6.0 버전의 function 함수 형태의 생성자 또한 마찬가지였습니다.

 

이때, 개발자의 실수로 오타가 나서, 생성자 함수를 그냥 일반 함수로 만들어서 베포 했습니다. 심지어, 해당 함수에는 이더(ether)를 전송하거나, 컨트렉트의 주인을 설정하는 코드가 있었습니다. 개발자는 해당 함수를 생성자로 만들었다고 생각하면서 잊고 있었지만, 해커는 해당 함수를 발견하고 컨트렉트의 owner를 해커의 지갑주소로 바꿔 스마트컨트렉트를 해킹했습니다.

 

Ethernaut Fallout 문제를 제출하고, 문제풀이 부분을 다시 가면 위와 같은 문장으로 바뀌어 있는 걸 알 수 있습니다.

 

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:

Rubixi의 이야기는 Ethereum 생태계에서 매우 잘 알려진 사례입니다. 회사는 이름을 'Dynamic Pyramid'에서 'Rubixi'로 변경했지만 계약의 생성자 메서드 이름을 변경하지 않았습니다.

 

// 변경 전 스마트컨트렉트 이름
contract DynamicPyramid {
  address private owner;
  // 컨트렉트명과 함수명이 같음 = 생성자 역할
  function DynamicPyramid() { owner = msg.sender; }
  }

// 변경 후 스마트컨트렉트 이름
contract Rubixi {
  address private owner;
  // 컨트렉트명과 함수명이 다름 = 생성자 역할x
  function DynamicPyramid() { owner = msg.sender; }
  }

 

 

이와 같은 일은 실제로 이더리움 생태계에서 Rubixi 회사에서 발생했던 실제 사건입니다.

 

스마트컨트렉트 해킹은 거창한 보안 문제로 발생하지 않습니다. 스마트컨트렉트 개발자의 작은 실수와 안일함에서 발생합니다.

Ethernaut의 Fallout문제는 앞으로의 스마트컨트렉트 개발자에게 경각심을 일깨워주기 위해 만든 문제라고 생각합니다.

 

 

 

지금까지 The Ethernaut 퀴즈 풀이 두 번째"Fallout" 문제를 풀어봤습니다.

 


 

반응형

댓글