Skip to content
This repository was archived by the owner on May 8, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions ethernaut/gatekeeper/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
node_modules
.env
coverage
coverage.json
typechain
typechain-types

# Hardhat files
cache
artifacts

yarn.lock
43 changes: 43 additions & 0 deletions ethernaut/gatekeeper/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# GateKeeper

This gatekeeper introduces a few new challenges. Register as an entrant to pass this level.

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

contract GateKeeper {
address public entrant;

modifier gateOne() {
require(msg.sender != tx.origin, 'GateKeeper: gate 1 not passed');
_;
}

modifier gateTwo() {
require(msg.sender.code.length == 0, 'GateKeeper: gate 2 not passed');
_;
}

modifier gateThree(bytes8 _gateKey) {
require(
uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^
uint64(_gateKey) ==
type(uint64).max,
'GateKeeper: gate 3 not passed'
);
_;
}

function enter(
bytes8 _gateKey
) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
```

See solution in `SOLUTION.md`

[Inspired by](https://ethernaut.openzeppelin.com/level/0xf59112032D54862E199626F55cFad4F8a3b0Fce9)
8 changes: 8 additions & 0 deletions ethernaut/gatekeeper/SOLUTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# GateKeeper solution

1. To pass through the first gate, the caller has to be a smart contract. When a smart contract calls another smart contract, the `msg.sender` value changes to the address of the calling contract, whereas `tx.origin` remains the address of externally owned account. Thus we need to call `enter` from an intermediary smart contract.
2. However to pass the second gate this contract lenght should equal zero. This is achievable if the smart contract consists only of a constructor, and we will call the `enter` function directly from the constructor.
3. In the third gate `^` means XOR, or Exclusive or. If we apply the XOR operation in reverse order, we get the required gate key.

- `contracts/Exploit.sol` - intermediary smart contract
- `test/GateKeeper.test.ts` - testing the solution
14 changes: 14 additions & 0 deletions ethernaut/gatekeeper/contracts/Exploit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './GateKeeper.sol';

contract Exploit {
constructor(GateKeeper gateKeeper) {
bytes8 gateKey = bytes8(
type(uint64).max ^
uint64(bytes8(keccak256(abi.encodePacked(address(this)))))
);
require(gateKeeper.enter(gateKey), 'Failed to enter');
}
}
33 changes: 33 additions & 0 deletions ethernaut/gatekeeper/contracts/GateKeeper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GateKeeper {
address public entrant;

modifier gateOne() {
require(msg.sender != tx.origin, 'GateKeeper: gate 1 not passed');
_;
}

modifier gateTwo() {
require(msg.sender.code.length == 0, 'GateKeeper: gate 2 not passed');
_;
}

modifier gateThree(bytes8 _gateKey) {
require(
uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^
uint64(_gateKey) ==
type(uint64).max,
'GateKeeper: gate 3 not passed'
);
_;
}

function enter(
bytes8 _gateKey
) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
8 changes: 8 additions & 0 deletions ethernaut/gatekeeper/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

const config: HardhatUserConfig = {
solidity: "0.8.18",
};

export default config;
28 changes: 28 additions & 0 deletions ethernaut/gatekeeper/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "gatekeeper",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"@ethersproject/abi": "^5.4.7",
"@ethersproject/providers": "^5.4.7",
"@nomicfoundation/hardhat-chai-matchers": "^1.0.0",
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-etherscan": "^3.0.0",
"@typechain/ethers-v5": "^10.1.0",
"@typechain/hardhat": "^6.1.2",
"@types/chai": "^4.2.0",
"@types/mocha": ">=9.1.0",
"@types/node": ">=12.0.0",
"chai": "^4.2.0",
"ethers": "^5.4.7",
"hardhat": "^2.13.0",
"hardhat-gas-reporter": "^1.0.8",
"solidity-coverage": "^0.8.0",
"ts-node": ">=8.0.0",
"typechain": "^8.1.0",
"typescript": ">=4.5.0"
}
}
26 changes: 26 additions & 0 deletions ethernaut/gatekeeper/test/GateKeeper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { expect } from 'chai';
import { ethers } from 'hardhat';
import { GateKeeper } from '../typechain-types';

describe('GateKeeper', function () {
let gateKeeper: GateKeeper;

async function deploy() {
const GateKeeperFactory = await ethers.getContractFactory('GateKeeper');
gateKeeper = await GateKeeperFactory.deploy();
}

before(deploy);

it('entrant should not be set on GateKeeper', async () => {
expect(await gateKeeper.entrant()).to.equal(ethers.constants.AddressZero);
});

it('entrant should pass through the GateKeeper', async () => {
const [deployer] = await ethers.getSigners();
const ExploitFactory = await ethers.getContractFactory('Exploit');
const exploit = await ExploitFactory.deploy(gateKeeper.address);
await exploit.deployed();
expect(await gateKeeper.entrant()).to.equal(deployer.address);
});
});
11 changes: 11 additions & 0 deletions ethernaut/gatekeeper/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true
}
}