関連インシデント
Analysis of the DAO exploit
Phil Daian
So I'm sure everyone has heard about the big news surrounding the DAO getting taken to the tune of $150M by a hacker using the recursive Ethereum send exploit.
This post will be the first in what is potentially a series, deconstructing and explaining what went wrong at the technical level while providing a timeline tracing the actions of the attacker back through the blockchain. This first post will focus on how exactly the attacker stole all the money in the DAO.
A Multi-Stage Attack This exploit in the DAO is clearly not trivial; the exact programming pattern that made the DAO vulnerable was not only known, but fixed by the DAO creators themselves in an earlier intended update to the framework's code. Ironically, as they were writing their blog posts and claiming victory, the hacker was preparing and deploying an exploit that targeted the same function they had just fixed to drain the DAO of all its funds. Let's get into the overview of the attack. The attacker was analyzing DAO.sol, and noticed that the 'splitDAO' function was vulnerable to the recursive send pattern we've described above: this function updates user balances and totals at the end, so if we can get any of the function calls before this happens to call splitDAO again, we get the infinite recursion that can be used to move as many funds as we want (code comments are marked with XXXXX, you may have to scroll to see em): function splitDAO ( uint _proposalID , address _newCurator ) noEther onlyTokenholders returns ( bool _success ) { ... uint fundsToBeMoved = ( balances [ msg . sender ] * p . splitData [ 0 ]. splitBalance ) / p . splitData [ 0 ]. totalSupply ; if ( p . splitData [ 0 ]. newDAO . createTokenProxy . value ( fundsToBeMoved )( msg . sender ) == false ) throw ; ... Transfer ( msg . sender , 0 , balances [ msg . sender ]); withdrawRewardFor ( msg . sender ); totalSupply -= balances [ msg . sender ]; balances [ msg . sender ] = 0 ; paidOut [ msg . sender ] = 0 ; return true ; } The basic idea is this: propose a split. Execute the split. When the DAO goes to withdraw your reward, call the function to execute a split before that withdrawal finishes. The function will start running without updating your balance, and the line we marked above as "the attacker wants to run more than once" will run more than once. What does that do? Well, the source code is in TokenCreation.sol, and it transfers tokens from the parent DAO to the child DAO. Basically the attacker is using this to transfer more tokens than they should be able to into their child DAO. How does the DAO decide how many tokens to move? Using the balances array of course: uint fundsToBeMoved = ( balances [ msg . sender ] * p . splitData [ 0 ]. splitBalance ) / p . splitData [ 0 ]. totalSupply ; Because p.splitData[0] is going to be the same every time the attacker calls this function (it's a property of the proposal p, not the general state of the DAO), and because the attacker can call this function from withdrawRewardFor before the balances array is updated, the attacker can get this code to run arbitrarily many times using the described attack, with fundsToBeMoved coming out to the same value each time. The first thing the attacker needed to do to pave the way for his successful exploit was to have the withdraw function for the DAO, which was vulnerable to the critical recursive send exploit, actually run. Let's look at what's required to make that happen in code (from DAO.sol): function withdrawRewardFor ( address _account ) noEther internal returns ( bool _success ) { if (( balanceOf ( _account ) * rewardAccount . accumulatedInput ()) / totalSupply < paidOut [ _account ]) throw ; uint reward = ( balanceOf ( _account ) * rewardAccount . accumulatedInput ()) / totalSupply - paidOut [ _account ]; if ( ! rewardAccount . payOut ( _account , reward )) throw ; paidOut [ _account ] += reward ; return true ; } If the hacker could get the first if statement to evaluate to false, the statement marked vulnerable would run. When that statements runs, code that looks like this would be called: function payOut ( address _recipient , uint _amount ) returns ( bool ) { if ( msg . sender != owner || msg . value > 0 || ( payOwnerOnly && _recipient != owner )) throw ; if ( _recipient . call . value ( _amount )()) { PayOut ( _recipient , _amount ); return true ; } else { return false ; } Notice how the marked line is exactly the vulnerable code mentioned in the description of the exploit we linked! That line would then send a message from the DAO's contract to "_recipient" (the attacker). "_recipient" would of course contain a default function, that would call splitDAO again with the same parameters as the initial call from the attacker. Remember that because this is all happening from inside withdrawFor from inside splitDAO, the code updating the balances in splitDAO hasn't run. So the split will send more tokens to the child DAO, and then ask for the reward