Skip to main content

Ethernaut Challenge

Learn how to stop common hacks.

Attacking a Contract

  1. Copy contract in Remix
  2. Create contractAttack.sol file
  3. Import Contract to attack
  4. Create reference to contract in the constructor
// TargetContractAttack.sol

import './TargetContract.sol';

contract TargetContractAttack {
TargetContract target;

constructor(address _targetAddr) public {
// DON'T USE NEW !!!
// When you don't use new you get a reference to an existing contract
target = TargetContract(_target);
}

// Attack logic
}

General strategy:

  • Claim ownership of the contract
  • Extract the balance

Or aim to be a nuisance and make the contract inoperable.

Challenges

ChallengeVulnerabilityBest PracticeUse Attack Contract
FallbackFallback Function
FalloutMistyped Constructor FunctionUse Constructor
Coin FlipUse an Oracle for Randomnesstrue
Telephone
Token
DelegationDelegateCallConsider impact on State
ForceselfdestructBe awaretrue
VaultCan decrypt PasswordsStore passwords off-chain
KingTransfer to malicious contractsHandle transfer failurestrue
Re-entrancyFailing Loop LogicUpdate Balances before Transfertrue
ElevatorUnderstand Interfacestrue
PrivacyUnderstand Slot Storagetrue
Gatekeeper OneTODO:
Gatekeeper TwoContract Initialisation & Bitwise comparisontrue
Naught CoinApprove Pattern
PreservationDelegateCall
Recovery
Magic Number
Alien Codex
Denial
Shop
Dex
Dex Two
Puzzle Wallet
Motorbike

Fallback

The default (fallback) function called if a contract is called without a function name or an incorrect function name is provided.

As a best practice this technique should be avoided in favour of providing a recieve function.

See soliditylang blog post

Solutiion

Send an amount of Eth into an unnamed transaction that passes a requirement to transfer owner to the calling address.

Then call withdraw.

contract.sendTransaction({value, 1});

Fallout

There was typo in creating the constructor name creating a public function by accident.

Best Practice use constructor as the contructor name not the name of the contract.

Coin Flip

Reviews randomness by trying to use blockhash

The problem is that anyone can investigate this number, as this is information of historical record.

With the blockhash info a hacker can decrypt codes that allow them to cheat logic.

Best practice: use audited data oracles.

Solution

Create a proxy contract in Remix to replicate the function logic and find the correct inputs to send into the original contract to win the game.

Delegation

Delegate call gives trust to another contract to change the state of the contract you are calling from.

Need to get sha3 of the function. Used web3.utils.sha3("pwn()")

To execute call with transaction when no method name specified.

Questions:

  • What is ethers equivalent of web3.utils?

Force

Contracts can be deleted from the blockchain by calling selfdestruct.

selfdestruct sends all remaining Ether stored in the contract to a designated address.

Vulnerability

A malicious contract can use selfdestruct to force sending Ether to any contract.

See solidity by example

// ForceAttack.sol

contract ForceAttack {
constructor () public payable {}

function attack(address _contractAddr) public {
selfdestruct(_contractAddr);
}
}

On deploying ForceAttack.sol then calling the attack function with the address of the Force.sol contract, the selfdestruct method of the contract will forward all ether in the ForceAttack.sol contract to (Force.sol) the address specified.

Potentially Unsafe Operations

When working with upgradeable smart contracts, you will always interact with the contract instance, and never with the underlying logic contract. However, nothing prevents a malicious actor from sending transactions to the logic contract directly. This does not pose a threat, since any changes to the state of the logic contracts do not affect your contract instances, as the storage of the logic contracts is never used in your project.

There is, however, an exception. If the direct call to the logic contract triggers a selfdestruct operation, then the logic contract will be destroyed, and all your contract instances will end up delegating all calls to an address without any code. This would effectively break all contract instances in your project.

A similar effect can be achieved if the logic contract contains a delegatecall operation. If the contract can be made to delegatecall into a malicious contract that contains a selfdestruct, then the calling contract will be destroyed.

As such, it is not allowed to use either selfdestruct or delegatecall in your contracts.

Vault

Passwords cannot be protected on the blockchain.

Use web3.getStorageAt function to investigate the storage of a contract. (Costs Gas)

web3.eth.getStorageAt(contract.address, 1, (err, result) => {
if (err) {
console.log(err);
} else {
console.log(web3.utils.toAscii(result));
}
});
tip

web3 is global object passed in by MetaMask

See commit reveal

King

When contract attempts to transfer Ether to a malicious contract, the contract will revert and lockup the other contract from processing further transactions because it does not handle error responses.

// AttackKing.sol

contract AttackKing {
constructor (address _king) public payable {
address(_king).call.value(msg.value)()
}

function external payable {
revert('You lose');
}
}

Re-Entrancy

The code to reduce a balance never executes, allowing another contract to maliciously drain the balance of the contract.

Prevention

The Checks Effects Interaction Pattern.

  1. Make state changes before calling external contracts (reduce balance before transfer).
  2. Implement a Mutex modifier by using ReentrancyGuard

Other solution examples:

tip

Use Transact on Remix to call a Fallback function

Elevator

Send a function that complies to an interface into a contract and manipulate the results.

Privacy

Search for a secret key. Essential to understand Slot Storage Best Practices

Understand

Web3.js Helper Snippet to investigate Contract storage.

let storage = [];

let callbackFNConstructor = (index) => (error, contractData) => {
storage[index] = contractData;
};

for (var i = 0; i < 6; i++) {
web3.eth.getStorageAt(contract.address, i, callbackFNConstructor(i));
}

Gatekeeper One

TODO:

Gatekeeper Two

Need to undertand how contracts are instantiated.

Naught Coin

Use approve and then transfer from to get around logic.

See the ER20 Approve Pattern

Preservation

Using DelegateCall helps when there is complex code that needs to be used from multiple different places.

It was possible to overwrite the code of a contract by calling the delegatecall function.

TODO: How can this be projeted? Change to private from public?

Best Practice

Related to Privacy above. Need to understand Slot Storage and DelegateCall.

If creating reusable code need to be very careful about how you use the DelegateCall.

Shared code should be written in a Library and not a Contract.

Recovery

address formed from the address of the creator.

  • Wallet Address
  • OR another Smart Contract Address

For example if factory contract, get that address to put into the formula below to find the contract address.

address = rightmost_20_bytes(kecca,(RLP(sender address, nonce)))

Magic Number

Alien Codex

Denial

Shop

Dex

Dex Two

Puzzle Wallet

Solution:

https://github.com/maAPPsDEV/puzzle-wallet-attack

Motorbike

Ethernaut's motorbike has a brand new upgradeable engine design.

selfdestruct the engine and make the motorbike unusable.

Guidance:

  • EIP-1967
  • UUPS upgradeable pattern
  • Initializable contract