神刀安全网

Analysis of the DAO exploit

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) {    ...   // XXXXX Move ether and assign new Tokens.  Notice how this is done first!   uint fundsToBeMoved =       (balances[msg.sender] * p.splitData[0].splitBalance) /       p.splitData[0].totalSupply;   if (p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender) == false) // XXXXX This is the line the attacker wants to run more than once       throw;    ...   // Burn DAO Tokens   Transfer(msg.sender, 0, balances[msg.sender]);   withdrawRewardFor(msg.sender); // be nice, and get his rewards   // XXXXX Notice the preceding line is critically before the next few   totalSupply -= balances[msg.sender]; // XXXXX AND THIS IS DONE LAST   balances[msg.sender] = 0; // XXXXX AND THIS IS DONE LAST TOO   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)) // XXXXX vulnerable     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)()) { // XXXXX vulnerable       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 to be withdrawn again. Which will try to send tokens to "_recipient" again, which would again call split DAO before updating the balances array.

And so it goes:

  1. Propose a split and wait until the voting period expires. (DAO.sol, createProposal)
  2. Execute the split. (DAO.sol, splitDAO)
  3. Let the DAO send your new DAO its share of tokens. (splitDAO -> TokenCreation.sol, createTokenProxy)
  4. Make sure the DAO tries to send you a reward before it updates your balance but after doing (3). (splitDAO -> withdrawRewardFor -> ManagedAccount.sol, payOut)
  5. While the DAO is doing (4), have it run splitDAO again with the same parameters as in (2) (payOut -> _recipient.call.value -> _recipient())
  6. The DAO will now send you more child tokens, and go to withdraw your reward before updating your balance. (DAO.sol, splitDAO)
  7. Back to (5)!
  8. Let the DAO update your balance. Because (7) goes back to (5), it never actually will :-).

(Side note: Ethereum’s gas mechanics don’t save us here. call.value passes on all the gas a transaction is working with by default, unlike the send function. so the code will run as long as the attacker will pay for it, which considering it’s a cheap exploit means indefinitely)

Armed with this, we can provide a step by step re-trace of how The DAO got emptied out.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Analysis of the DAO exploit

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址