Reentrancy Attacks
Exploitation
Reentrancy is the most famous smart contract vulnerability, responsible for the $60M DAO hack in 2016. It occurs when external contract calls are made before state updates, allowing recursive draining of funds.
Vulnerable Contract Example
solidity
// VULNERABLE: Reentrancy attack
contract VulnerableBank {
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");
// BAD: External call before state update
(bool success, ) = msg.sender.call{value: _amount}("");
require(success, "Transfer failed");
balances[msg.sender] -= _amount; // State update AFTER external call
}
}
// ATTACKER CONTRACT
contract Attacker {
VulnerableBank public bank;
constructor(address _bankAddress) {
bank = VulnerableBank(_bankAddress);
}
// 1. Deposit 1 ETH
function attack() external payable {
require(msg.value >= 1 ether);
bank.deposit{value: 1 ether}();
bank.withdraw(1 ether); // Trigger withdraw
}
// 2. Fallback function called when receiving ETH
// Recursively calls withdraw before balance is updated
receive() external payable {
if (address(bank).balance >= 1 ether) {
bank.withdraw(1 ether); // RECURSIVE CALL
}
}
}
// SECURE: Checks-Effects-Interactions pattern
contract SecureBank {
mapping(address => uint256) public balances;
function withdraw(uint256 _amount) public {
require(balances[msg.sender] >= _amount);
// 1. CHECKS (validation)
// 2. EFFECTS (state changes FIRST)
balances[msg.sender] -= _amount;
// 3. INTERACTIONS (external calls LAST)
(bool success, ) = msg.sender.call{value: _amount}("");
require(success);
}
}
// Or use ReentrancyGuard modifier from OpenZeppelin
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract ProtectedBank is ReentrancyGuard {
mapping(address => uint256) public balances;
function withdraw(uint256 _amount) public nonReentrant {
require(balances[msg.sender] >= _amount);
balances[msg.sender] -= _amount;
(bool success, ) = msg.sender.call{value: _amount}("");
require(success);
}
}The DAO Hack (2016)
The DAO (Decentralized Autonomous Organization) lost $60M ETH due to reentrancy. Attacker recursively withdrew
funds before balance was updated. This led to Ethereum hard fork (ETH vs ETC).
Prevention Methods
- Checks-Effects-Interactions: Update state before external calls
- ReentrancyGuard: Use OpenZeppelin's nonReentrant modifier
- Pull over Push: Let users withdraw instead of sending automatically
- Mutex Lock: Use boolean flag to prevent reentry