Analysis: 15 min | Discussion: 10 min | Wrap-up: 5 min
Group Size: 2-3 students | Materials: This handout only (all-in-one)
Learning Objectives
- Identify reentrancy and access control vulnerabilities in smart contracts
- Understand how these bugs enable multi-million dollar hacks
- Propose practical security fixes
Activity Structure
Read both contracts below. For each one, identify the vulnerability, explain how an attacker could exploit it, and propose a fix. Fill in the worksheet at the end.
Share findings with the class. Each group presents one vulnerability (2-3 minutes). Discuss which is more severe and why.
Instructor reveals real-world hacks using these exact vulnerabilities (The DAO: $60M, Poly Network: $600M)
Reentrancy: External call before state update → attacker can call back before balance changes
Access Control: Missing permission checks → anyone can call privileged functions
Contract 1: Bank Withdrawal System
contract Bank {
mapping(address => uint256) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
// Transfer funds to user
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
// Update balance
balances[msg.sender] -= amount;
}
}
The contract sends ETH before updating the balance. This allows an attacker to call withdraw() again in a fallback function before the balance is decreased.
✅ The Fix:Update the balance before sending ETH (checks-effects-interactions pattern):
balances[msg.sender] -= amount; // Update FIRST
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
🎯 Real-World Example:
The DAO Hack (2016): $60 million stolen using this exact vulnerability. Led to Ethereum hard fork.
Contract 2: Token Ownership Manager
contract TokenManager {
address public owner;
bool public paused;
mapping(address => uint256) public balances;
constructor() {
owner = msg.sender;
}
function mint(address to, uint256 amount) public {
require(msg.sender == owner, "Only owner can mint");
balances[to] += amount;
}
function setPaused(bool _paused) public {
require(msg.sender == owner, "Only owner");
paused = _paused;
}
function setOwner(address newOwner) public {
// Transfer ownership to new address
owner = newOwner; // ⚠️ MISSING ACCESS CONTROL
}
function transfer(address to, uint256 amount) public {
require(!paused, "Transfers paused");
balances[msg.sender] -= amount;
balances[to] += amount;
}
}
The setOwner() function is missing require(msg.sender == owner).
Anyone can call it and become owner, gaining unlimited minting power.
Add access control check:
function setOwner(address newOwner) public {
require(msg.sender == owner, "Only owner"); // ADD THIS
require(newOwner != address(0), "Invalid address");
owner = newOwner;
}
🎯 Real-World Example:
Poly Network Hack (2021): $600 million exploited through access control bug allowing attacker to become admin.
Worksheet - Submit at End of Class
Contract 1: Bank Withdrawal (Reentrancy)
1. Explain the vulnerability in your own words (4 points): 2. How would an attacker exploit this? Describe step-by-step (4 points): 3. What's the fix? (2 points):Contract 2: Token Manager (Access Control)
1. Explain the vulnerability in your own words (4 points): 2. How would an attacker exploit this? Describe step-by-step (4 points): 3. What's the fix? (2 points):Comparative Analysis
Which vulnerability is more severe? Why? Consider impact, likelihood, and difficulty to exploit (Participation credit):Grading Rubric (20 Points Total)
| Component | Points | Criteria |
|---|---|---|
| Contract 1 Analysis | 10 | Vulnerability explanation (4), Attack scenario (4), Fix (2) |
| Contract 2 Analysis | 10 | Vulnerability explanation (4), Attack scenario (4), Fix (2) |
| TOTAL | 20 | 100% |
- Be specific: "External call before state update" beats "there's a bug"
- Think like an attacker: What would you do if you wanted to steal funds?
- Line-by-line analysis: Read code carefully, checking what executes in what order
- Use the reference: The vulnerability descriptions above are your guide
For Instructors: Teaching Notes
| Introduction & instructions | 3 minutes |
| Student work time (groups analyze contracts) | 15 minutes |
| Group sharing (3-4 groups × 2-3 min each) | 10 minutes |
| Wrap-up: Real-world examples & key takeaways | 2 minutes |
| TOTAL | 30 minutes |
- Stakes are real: These vulnerabilities have cost billions in actual hacks
- Pattern recognition: Security comes from knowing what to look for
- Both are CRITICAL: Either vulnerability can destroy a protocol
- Prevention is cheaper: Bug bounty ($500K) << exploit ($50M) << recovery (impossible)
- "Why do you think developers still make these mistakes?"
- "If you found this bug, would you exploit it or report it? Why?"
- "How is smart contract security different from web app security?"
- "What does this teach us about mechanism design?"
- Add integer overflow contract (Contract 3 from full version)
- Have students write attacker contracts in pseudocode
- Discuss bug bounty economics in depth
- Assign full version as homework
Contract 1: Reentrancy - external call before balance update. Fix: reverse order.
Contract 2: Missing access control on setOwner(). Fix: add require(msg.sender == owner).
Severity: Both CRITICAL. Accept either as "most severe" with valid reasoning.
© Joerg Osterrieder 2025-2026. All rights reserved.