Ethernaut CTF Part-1!
1 - 10 challenges
Ethernaut CTF Part-1
Introduction
I’ve started doing work arounds on blockchain, as a part of it I started solving Ethernaut CTF, as this is long making this into parts.
TLDR;
0.Hello
Vulnerability Concept: No actual vulnerability — this level is for setup, interaction, and basic Solidity understanding. It’s about exploring and understanding the Ethernaut environment, ABI, and on-chain data.
Exploit Mechanism: Interact with the contract using the console to:
- Navigate function calls step-by-step (info → info1 → info2 → info42 → method7123949)
- Read the private password from storage slot 0
- Pass the password to
authenticate()to clear the level
Real-World Example: Understanding that private variables in Solidity are not really hidden — they’re stored on-chain and can be read by anyone. Also introduces contract introspection using web3 or ethers.js.
My Attack Summary (in 2-3 steps):
- Used
await contract.info()and followed function hints - Read
passwordfrom slot 0 usingweb3.eth.getStorageAt(instance.address, 0) - Called
authenticate(password)to complete the level
Solidity Snippet I learned:
function authenticate(string memory passkey) public {
if (keccak256(abi.encodePacked(passkey)) == keccak256(abi.encodePacked(password))) {
cleared = true;
}
}
1.Fallback
Vulnerability Concept:
Misused receive() fallback function allows ownership transfer when certain conditions are met. Ownership logic is unintentionally duplicated in the fallback, creating a secondary path to become owner.
Exploit Mechanism:
- Make a small contribution using
contribute()(less than 0.001 ETH) - Send ETH directly to the contract to trigger the
receive()function - This changes the
ownertomsg.senderif contribution was > 0 - Call
withdraw()to drain the contract balance
Real-World Example: Poorly designed fallback functions in contracts can become unauthorized entry points. This mimics real DeFi attacks where fallback/receive is over-permissive or improperly guarded.
My Attack Summary (in 2-3 steps):
- Called
contribute()with a tiny amount to satisfycontributions[msg.sender] > 0 - Sent ETH directly to the contract address (triggered
receive()), becameowner - Called
withdraw()and drained the contract balance
Solidity Snippet I learned:
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
This showed me how fallback functions can be dangerous when they change critical contract state like ownership.
2.Fallout
Vulnerability Concept:
Incorrect constructor declaration in older Solidity versions — Fal1out() is just a public function, not a constructor, due to a typo. This allows anyone to call it after deployment and set themselves as the contract owner.
Exploit Mechanism:
- Identify that
Fal1out()is not a constructor (misspelled, lowercaseLand digit1) - Call
Fal1out()directly — it setsowner = msg.sender - Now that you’re owner, you can call
collectAllocations()to drain the contract
Real-World Example:
Before Solidity 0.4.22 introduced the constructor keyword, constructor functions relied on matching the contract name exactly. Typos allowed malicious actors to claim ownership post-deployment — seen in older DeFi codebases.
My Attack Summary (in 2-3 steps):
- Called the misnamed
Fal1out()function directly to become owner - Verified ownership with the
owner()public getter - Called
collectAllocations()to transfer contract balance to myself
Solidity Snippet I learned:
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}
3.CoinFlip
Vulnerability Concept:
Predictable randomness using blockhash(block.number - 1) — this value is deterministic and publicly accessible on-chain, making it possible to compute the same result as the contract and always win the flip.
Exploit Mechanism:
- The contract derives the “random” coin flip from
blockhash(block.number - 1)divided by a known constantFACTOR - This hash is available to all contracts, so an attacker contract can calculate the same result
- By predicting the flip outcome before calling
flip(), you can win repeatedly
Real-World Example:
Multiple real DeFi and gaming contracts have been exploited due to on-chain randomness misuse (e.g., games using block.timestamp, block.number, or blockhash() for RNG).
My Attack Summary (in 2-3 steps):
- Created an attack contract that reads
blockhash(block.number - 1)and calculates the expected outcome using the same logic asCoinFlip - Sent that guess to the target contract via
flip() - Won flips repeatedly by submitting the correct prediction every time
Solidity Snippet I learned:
uint256 blockValue = uint256(blockhash(block.number - 1));
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1;
4.Telephone
Vulnerability Concept:
Improper use of tx.origin for ownership authentication — leads to logic bypass when function is called via contract.
Exploit Mechanism:
Call changeOwner() through an attacker contract so that msg.sender != tx.origin, satisfying the contract’s condition and allowing ownership takeover.
Real-World Example:
Phishing attacks where malicious contracts exploit tx.origin checks to perform unauthorized actions on behalf of unsuspecting users (e.g., Parity Multisig vulnerability pattern).
My Attack Summary (in 2-3 steps):
- Deploy a contract that calls
changeOwner(tx.origin)on the target. - Call that contract from your EOA (externally owned account).
- Since
msg.sender(contract) ≠tx.origin(EOA), condition passes and owner becomes EOA.
Solidity Snippet I learned:
function attack() external {
telephone.changeOwner(tx.origin);
}
5.Token
Vulnerability Concept: Integer underflow — in Solidity <0.8.0, subtracting a larger number from a smaller one wraps around to a huge value. This breaks balance checks and allows attackers to gain unearned tokens.
Exploit Mechanism:
- The contract checks
require(balances[msg.sender] - _value >= 0)
This looks safe, but in Solidity 0.6.0, underflow wraps to maxuint256 - Call
transfer()with_value > balance(e.g., transfer 21 tokens with only 20 in balance) balances[msg.sender]underflows → becomes huge number- This lets you send massive value and claim balance you didn’t have
Real-World Example:
Before Solidity 0.8.x added automatic overflow/underflow protection, many DeFi hacks abused this behavior. For example, the 2017 Parity Wallet vulnerability was partly caused by unsafe math operations.
My Attack Summary (in 2-3 steps):
- I started with 20 tokens (default)
- Called
transfer(<any address>, 21)— underflow bypassed the balance check balances[msg.sender]became huge — effectively giving me infinite tokens
Solidity Snippet I learned:
require(balances[msg.sender] - _value >= 0); // looks safe, but unsafe in Solidity <0.8.0
balances[msg.sender] -= _value;
balances[_to] += _value;
6.Delegation
Vulnerability Concept:
Misuse of delegatecall in fallback allows unauthorized execution of external contract logic in the caller’s storage context.
Exploit Mechanism:
Send the function selector for pwn() directly to the Delegation contract. The fallback triggers, performing a delegatecall to Delegate, executing pwn() in the storage of Delegation, and setting owner = msg.sender.
Real-World Example: Improper proxy setups or upgradeable contracts have suffered this — notably, the Parity Multisig Wallet hack (2017) where the delegate context overwrote critical storage.
My Attack Summary (in 2-3 steps):
- Sent
0xdd365b8b(pwn()selector) toDelegationfrom EOA fallback()was triggered →delegatecalltoDelegate.pwn()- Storage of
Delegationupdated →owner = my EOA
Solidity Snippet I learned:
fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
}
7.Force
Vulnerability Concept:
A contract cannot prevent receiving Ether sent via selfdestruct(). This bypasses normal transfer logic, including receive() and fallback() functions.
How Ether Can Be Sent to a Contract:
- During contract deployment
Using apayable constructor, you can send ETH to the contract at the time of deployment. - By receiving ETH through
receive()orfallback()
The contract must implement these functions with thepayablemodifier to accept ETH directly via calls or transfers. - Using
selfdestruct(address)from another contract
Ether is forcefully sent to the target address when a contract self-destructs. This method cannot be refused, even if the target contract lacks payable functions. - (Advanced) Set
block.coinbase = contractAddress
If the miner chooses your contract address as the block reward recipient, the block reward ETH is transferred there.
Exploit Mechanism:
Deploy a contract with ETH and call selfdestruct(payable(target)). The ETH is forcefully transferred to the target contract’s address, even if it has no payable functions or fallback.
Real-World Example:
Used in griefing or poisoning attacks — e.g., breaking logic in vaults or DeFi contracts that assume address(this).balance == 0 or control over incoming funds.
My Attack Summary (in 2-3 steps):
- Created and deployed a contract with a
payableconstructor and aselfdestruct()call targeting the Force contract. - Funded the attack contract with ETH at deployment.
- Called
attack()→ triggeredselfdestruct()→ ETH forcibly sent to the Force contract.
Why We Use selfdestruct:
- Bypasses contract logic
ETH is sent regardless of whether the contract has a payable function or not. - Cannot be refused
The target contract has no way to prevent receiving Ether this way. - Enables forced ETH injection
Useful in CTFs and real-world exploits where you want to “break” assumptions likebalance == 0in contracts that seem ETH-proof.
Solidity Snippet I learned:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ForceAttack {
constructor () public payable {}
function attack(address payable _contract) public {
selfdestruct(_contract);
}
}
8.Vault
Vulnerability Concept:
Solidity’s private visibility does not prevent on-chain access. All contract storage, even private variables, is publicly accessible by anyone who knows the storage slot index.
Exploit Mechanism:
Use off-chain tooling (web3.eth.getStorageAt) to read the password stored in slot 1, then call unlock(password) from any EOA or contract. Solidity contracts cannot read another contract’s storage directly.
Real-World Example:
Many contracts that store sensitive information (like admin secrets or cryptographic keys) have been compromised because of improper assumptions about private visibility in Solidity. Example: early DeFi wallets exposing private variables on-chain.
My Attack Summary (in 2–3 steps):
- Located the password in storage slot 1 using
web3.eth.getStorageAt(contractAddress, 1) - Retrieved the password value from chain state using JavaScript/Web3 tools
- Called
unlock(bytes32 password)using the retrieved value → contract unlocked
Solidity Snippet I Learned:
function unlock(bytes32 _password) public {
if (password == _password) {
locked = false;
}
}
9.King
Vulnerability Concept:
Denial of Service via failing .transfer() — the contract assumes the king can always receive Ether.
Exploit Mechanism:
Deploy a contract with no receive() or one that explicitly revert()s on receiving Ether. This makes the King contract unable to send Ether to the new king, locking the throne permanently.
Real-World Example:
This is a textbook DoS case — relying on .transfer() in smart contracts can break if the recipient reverts (e.g., Gnosis Safe in early versions).
My Attack Summary (in 2–3 steps):
- Deployed a contract that calls the King contract with > prize.
- That contract becomes king, but reverts on further ETH.
- Ethernaut can no longer dethrone it — challenge passes.
Solidity Snippet I Learned:
contract KingBlocker {
constructor(address target) payable {
(bool success,) = target.call{value: msg.value}("");
require(success, "Failed to become king");
}
// Revert any ETH sent in the future
receive() external payable {
revert("Can't dethrone me.");
}
10.Reentrancy
Vulnerability Concept:
Classic reentrancy — contract sends Ether before updating state (balances[msg.sender]), allowing recursive withdrawal.
Exploit Mechanism:
Create a contract that donates ETH and triggers withdraw(). In the receive() function, recursively call withdraw() again before the balance is updated.
Real-World Example:
Similar to TheDAO hack in 2016 — reentrancy remains one of the most dangerous vulnerabilities if not mitigated with checks-effects-interactions or ReentrancyGuard.
My Attack Summary (in 2–3 steps):
- Wrote a contract that donates ETH to Reentrance.
- Called
withdraw()withmsg.valuestored as state. - On receiving ETH, re-entered until Reentrance was drained.
Solidity Snippet I Learned:
contract ReentrancyAttack {
Reentrance public reentrancy;
uint public attackAmount;
constructor(address payable _target) public {
reentrancy = Reentrance(_target);
}
function attack() public payable {
require(msg.value > 0, "Need ETH");
attackAmount = msg.value;
reentrancy.donate{value: msg.value}(address(this));
reentrancy.withdraw(msg.value);
}
receive() external payable {
if (address(reentrancy).balance > 0) {
reentrancy.withdraw(attackAmount);
}
}
}