Ethernaut Challenge
Learn how to stop common hacks.
Attacking a Contract
- Copy contract in Remix
- Create contractAttack.sol file
- Import Contract to attack
- 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
Challenge | Vulnerability | Best Practice | Use Attack Contract |
---|---|---|---|
Fallback | Fallback Function | ||
Fallout | Mistyped Constructor Function | Use Constructor | |
Coin Flip | Use an Oracle for Randomness | true | |
Telephone | |||
Token | |||
Delegation | DelegateCall | Consider impact on State | |
Force | selfdestruct | Be aware | true |
Vault | Can decrypt Passwords | Store passwords off-chain | |
King | Transfer to malicious contracts | Handle transfer failures | true |
Re-entrancy | Failing Loop Logic | Update Balances before Transfer | true |
Elevator | Understand Interfaces | true | |
Privacy | Understand Slot Storage | true | |
Gatekeeper One | TODO: | ||
Gatekeeper Two | Contract Initialisation & Bitwise comparison | true | |
Naught Coin | Approve Pattern | ||
Preservation | DelegateCall | ||
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.
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.
// 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));
}
});
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.
- Make state changes before calling external contracts (reduce balance before transfer).
- Implement a Mutex modifier by using ReentrancyGuard
Other solution examples:
- Solidity by Example
- Checks Effects and Interactions - Fravoll
- Protect your Smart Contracts from Re-entrancy Attacks
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
- Slot Storage
- Type Conversions
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