Pwnstar

Ethernaut Level 2(Fal1out) 본문

Wargame/Ethernaut

Ethernaut Level 2(Fal1out)

포너블처돌이 2023. 2. 21. 00:31

버그헌팅하다가 문제 푸는게 좀 늦어졌다.

마찬가지로 컨트랙트의 ownership을 획득해야하는 문제로 보인다

Remix IDE가 도움이 될거라고 하는데 코드 분석을 하고 한번 Remix에 돌려보자.

// 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];
  }
}

저번 문제보다는 코드가 좀 짧다.

우선

import 'openzeppelin-contracts-06/math/SafeMath.sol';

SafeMath.sol 이라는 파일을 import해서 사용하려는 것으로 보인다.

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

using X for Y; 는 X 라이브러리를 Y 타입으로 사용한다는 뜻이다.

SafeMath는 openzeppelin에서 개발한 라이브러리인 모양이다. github에서 소스코드를 찾아볼 수 있었다.

openzeppelin-contracts/SafeMath.sol at master · OpenZeppelin/openzeppelin-contracts
OpenZeppelin Contracts is a library for secure smart contract development. - openzeppelin-contracts/SafeMath.sol at master · OpenZeppelin/openzeppelin-contracts
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol
  • SafeMath.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.6.0) (utils/math/SafeMath.sol)
    
    pragma solidity ^0.8.0;
    
    // CAUTION
    // This version of SafeMath should only be used with Solidity 0.8 or later,
    // because it relies on the compiler's built in overflow checks.
    
    /**
     * @dev Wrappers over Solidity's arithmetic operations.
     *
     * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
     * now has built in overflow checking.
     */
    library SafeMath {
        /**
         * @dev Returns the addition of two unsigned integers, with an overflow flag.
         *
         * _Available since v3.4._
         */
        function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
            unchecked {
                uint256 c = a + b;
                if (c < a) return (false, 0);
                return (true, c);
            }
        }
    
        /**
         * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
         *
         * _Available since v3.4._
         */
        function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
            unchecked {
                if (b > a) return (false, 0);
                return (true, a - b);
            }
        }
    
        /**
         * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
         *
         * _Available since v3.4._
         */
        function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
            unchecked {
                // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                // benefit is lost if 'b' is also tested.
                // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                if (a == 0) return (true, 0);
                uint256 c = a * b;
                if (c / a != b) return (false, 0);
                return (true, c);
            }
        }
    
        /**
         * @dev Returns the division of two unsigned integers, with a division by zero flag.
         *
         * _Available since v3.4._
         */
        function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
            unchecked {
                if (b == 0) return (false, 0);
                return (true, a / b);
            }
        }
    
        /**
         * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
         *
         * _Available since v3.4._
         */
        function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
            unchecked {
                if (b == 0) return (false, 0);
                return (true, a % b);
            }
        }
    
        /**
         * @dev Returns the addition of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `+` operator.
         *
         * Requirements:
         *
         * - Addition cannot overflow.
         */
        function add(uint256 a, uint256 b) internal pure returns (uint256) {
            return a + b;
        }
    
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting on
         * overflow (when the result is negative).
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         *
         * - Subtraction cannot overflow.
         */
        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
            return a - b;
        }
    
        /**
         * @dev Returns the multiplication of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `*` operator.
         *
         * Requirements:
         *
         * - Multiplication cannot overflow.
         */
        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
            return a * b;
        }
    
        /**
         * @dev Returns the integer division of two unsigned integers, reverting on
         * division by zero. The result is rounded towards zero.
         *
         * Counterpart to Solidity's `/` operator.
         *
         * Requirements:
         *
         * - The divisor cannot be zero.
         */
        function div(uint256 a, uint256 b) internal pure returns (uint256) {
            return a / b;
        }
    
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * reverting when dividing by zero.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         *
         * - The divisor cannot be zero.
         */
        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
            return a % b;
        }
    
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
         * overflow (when the result is negative).
         *
         * CAUTION: This function is deprecated because it requires allocating memory for the error
         * message unnecessarily. For custom revert reasons use {trySub}.
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         *
         * - Subtraction cannot overflow.
         */
        function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            unchecked {
                require(b <= a, errorMessage);
                return a - b;
            }
        }
    
        /**
         * @dev Returns the integer division of two unsigned integers, reverting with custom message on
         * division by zero. The result is rounded towards zero.
         *
         * Counterpart to Solidity's `/` operator. Note: this function uses a
         * `revert` opcode (which leaves remaining gas untouched) while Solidity
         * uses an invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         *
         * - The divisor cannot be zero.
         */
        function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            unchecked {
                require(b > 0, errorMessage);
                return a / b;
            }
        }
    
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * reverting with custom message when dividing by zero.
         *
         * CAUTION: This function is deprecated because it requires allocating memory for the error
         * message unnecessarily. For custom revert reasons use {tryMod}.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         *
         * - The divisor cannot be zero.
         */
        function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            unchecked {
                require(b > 0, errorMessage);
                return a % b;
            }
        }
    }

생성자

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

allocate function

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

allocate 함수는 payable 함수이고 msg.sender가 전송한 이더를 allocations[msg.sender] 매핑 변수에 더해주는 함수

sendAllocation function

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

sendAlloction 함수는 address를 매개변수로 받고 함수가 실행되기 위한 조건이 하나 붙는다. allocations[allocator] 매핑 값이 0보다 크면 transfer 함수로 allocator에게 allocations[allocator] 매핑의 값을 전송한다.

collectAllocations function

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

owner만 호출할 수 있고 msg.sender에게 this의 balance를 전송한다.

allocatorBalance function

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

현재 allocator의 allocations 매핑 변수 값을 알려준다.

코드가 짧아 분석은 이렇게 단간하긴한데, 어디가 취약점인지 아직 모르겠다.

그래서 SafeMath와 문제 소스코드를 가져와서 Remix IDE에 넣고 컴파일 후 실행시켜보았다.

이렇게 나오는데 함수의 개수가 좀 많다. 위에 코드랑 비교해보니 하나 더 많은데

Fal1out이라는 함수가 존재한다. 이게 코드 분석할 때 저번 문제와 너무 비슷해서 그냥 넘어갔는데, 다시 한 번 보자.

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

생성자라고 주석도 달아놔서 아 생성자구나 하고 넘어갔는데 function이 붙은거 보니 함수네?

코드를 보면 그냥 컨트랙트 호출자를 owner로 만들어준다. 바로 이 함수 이용하면 ownership을 획득할 수 있다.

그러면 현재 contract의 owner는 0x00000000000000000000이니 Fal1out 함수를 호출하여 owner를 바꿀 수 있는 지 보자

명령어는 다음과 같이 입력했다. 함수가 payable이기 때문에 이후 호출할 collectAllocations함수를 이용하기 위해 sendTransaction함수로 0.0001 이더를 전송하였다.

await contract.Fal1out.sendTransaction({from:"0x3501a130831dd493CaBf25FEe9a7Ad4D1F7A3e35",to:"0x1fce3f95e036C0aB2D6FF9210282B5F847C4c32E",value:toWei("0.0001")})

owner가 나의 주소로 바뀐 것을 확인할 수 있었다.

이제 onlyOwner만 호출할 수 있는 collectAllocations 함수로 내가 보낸 이더를 다시 나에게 전송하면 문제를 풀 수 있을 것 같다.

중간에 명령어 하나 입력한게 있는데 이건 무시하면 되고, collectAllocations 함수 호출이 완료되었으니 submit을 진행하면

문제를 풀었다는 메시지가 나온다.


Uploaded by N2T

'Wargame > Ethernaut' 카테고리의 다른 글

Ethernaut Level 4(Telephone)  (0) 2023.03.12
Ethernaut Level 3(Coin Flip)  (1) 2023.03.09
Ethernaut Level 1(Fallback)  (0) 2023.01.31
Ethernaut Level 0(Hello Ethernaut)  (0) 2023.01.27
Ethernaut 문제풀이를 위한 환경설정  (0) 2023.01.27
Comments