Forbes magazine logo Ranked Best Coding Bootcamps 2023

DAO Split Attack Explanation

Altcademy Team wrote on 7 February 2018

This is a javascript implementation of the recent Dao attack.

Modified with some explanation https://repl.it/C4I8/1
var baseBalance = 1000; var reward = 10; var times = 0; var actualWithdrewAmount = 0; function sender() { if (times < 30) { times++; withdrawBalance(); } else { times = 0; } function foo() { } foo.value = function(amount) { return function() { if (amount > 0) { actualWithdrewAmount += amount; console.log('withdrawing ' + amount); return true; } else { console.log('none'); return false; } } } return foo } function withdrawBalance() { amountToWithdraw = reward if (!(sender().value(amountToWithdraw)())) { throw Error } baseBalance -= reward; reward = 0; } withdrawBalance() console.log('base balance after attack: ' + baseBalance) console.log('actualWithdrewAmount: ' + actualWithdrewAmount);

The SplitDao function of Dao calls withdrawRewardFor(msg.sender) inside it which actually calls the message sender contract to send reward to it.
The attacker can implement a loop inside the general function of its contract (which becomes msg.sender above) to call split DAO again and again creating a recursive call stack.

And because the msg.sender's balance is only zeroed out after his eth balance is sent to the childDao and withdrawRewardFor(). The recursive call on SplitDao will send the attacker's original amount to the child dao until call stack reaches max limit or the attacker stops paying for gas.

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; }

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; }

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; } }

I have made a Javascript implementation of this attack here https://repl.it/C4I8/1

Trusted by

Students and instructors from world-class organizations

Imperial College London
Carnegie Mellon University
City University of Hong Kong
Hack Reactor
Cisco Meraki
University of Oxford
Swift
Bazaarvoice
Waterloo
Uber
AtlanTech
Tumblr
Boston College
Bombardier Aerospace
University of St. Andrews
New York University
Minerva Schools at KGI
Merrill Lynch
Riot Games
JP Morgan
Morgan Stanley
Advanced Placement®
Google
KPMG
The University of Hong Kong
University of Toronto
SCMP
Moat
Zynga
Hello Toby
Deloitte
Goldman Sachs
Yahoo
HSBC
General Assembly
Tesla
McGill University
Microsoft

Join the upcoming Cohort #89

Enroll for May 6th, 2024