This post is a follow-up to my previous blog post that explains the principles and timeline of the DAO exploit: http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/ (http://www.8btc.com/thedao-exploit-analysis) Secondary invasion In the above, I mainly focused on the recursive send of the withdrawRewardFor vulnerability of the DAO, which is also the focus of media and researchers today. In the above, I described an attack that can be amplified by an attacker 30 times and can be repeated in an infinite loop. I initially thought the implementation details were unimportant, but Joey Krug and Martin Köppelmann did a great job showing how important these details are. As Martin points out, there are indeed two separate exploits that enable a third, more powerful vulnerability.
The attacker implemented the third one, which we have discussed before, but let's look at the second one. We create a contract where the first and third vulnerabilities have been fixed. Let's look at withdrawRewardFor, a perfect contract with no loop entry vulnerability. Let's recall my previous post that the vulnerability of withdrawRewardFor made splitDAO in DAO 1.1 reentrant, and even though it may have been fixed, the problem still exists in the first line of this function:
Keep in mind that even if there is no balance in the rewards account and the amount of money paid to the user is 0, the call to payOut still runs and still calls arbitrary code in the recipient's contract:
The first time getRewardFor runs, it will pay out the legitimate reward. The second time it pays out 0, the third time it pays out 0, the fourth time it pays out 0, and so on. It will also allow the user to return to splitDAO, amplifying what they should have received by 30x. Note that even the fixed withdrawRewardFor in DAO 1.1 still has the reentrancy vulnerability. This reentrancy does not threaten the balance in the reward account, because the second (third, fourth) time you run this function it pays 0. However, it is still vulnerable to reentrancy attacks. If we fix this in DAO 1.2, we write the withdrawRewardFor function perfectly so that it is not vulnerable to reentrancy. This function should look like this:
Only the red part is modified, simply changing < to ≤. Why? The second time you run this function, the value of paidOut[_account] will be equal to the value of the expression on the left. The code has been set before the reentry is triggered:
So this is what withdrawFor should look like for DAO 1.1, it has no reentrancy vulnerability, it is a classic anti-model best practice. This code is simple and easy to read. In this way, the infinite amplification weakness of splitDAO described in my previous article does not exist, and the attacker will not do this: because the payOut call will not be run when the balance is zero, there is no point in repeatedly running this function. However, as Joey Krug pointed out, this does not prevent the same exploit from being used to drain a DAO 1.2 using withdrawRewardFunction. The key is that it is possible to call arbitrary code once with withdrawRewardFor in the new case, and once is enough. The function itself amplifies this vulnerability: when your reward is withdrawn and the DAO's transfer function is called, your balance becomes zero after the token is transferred to your child DAO, and your token will be stored in a new account. The code details can be found in the previous article, mainly involving splitDAO and transfer functions, both of which modify the same balance array non-atomically. Therefore, any logic that affects a process non-atomically will cause a vulnerability, and if the process is interrupted by a call to execute an arbitrary contract, it will be attacked. So in addition to "writing non-reentrant functions", remember: don't call external contracts, otherwise you can't predict your program flow or state. There are a few interesting things about this exploit. First, it's slow. Essentially, you're doubling your tokens every time you move them. However, this doesn't affect the success of the attack: it's fast enough, and by fast, nobody can stop you. Second, in the DAO 1.1 code that we have fixed, the reentrancy vulnerability is eliminated because it requires a balance in the reward account. If the rewardAccount.accumulatedInput() returns a value of 0, the user will never be paid, and our modified function will always return false, never executing potential malicious code. In the published DAO1.1 code, the withdrawRewardFor vulnerability still exists, and the reward account still does not need a balance to execute withdrawRewardFor. In practice, this doesn't matter, the reward account can be funded, intentionally or accidentally. This means that an attacker can transfer part of its reward each time, and can use the withdrawRewardFor function to break in again, even after the DAO 1.2 has been fixed. Disadvantages of Solidity If the first vulnerability I listed didn't exist, then the community would definitely miss the second one. The first one is an example of the anti-model, and we will try our best to prevent it at this point, and the second one is a more subtle example of the anti-model: no re-entrancy of the original function, just re-entrancy of the original contract. To summarize:
To me, the above means more than just flaws and vulnerabilities in the DAO contract itself. It means that, technically, these are features designed by the Ethereum virtual machine itself, but Solidity created security vulnerabilities in the contract that were not only ignored by the community, but also by the designers of the language. In my opinion, 50% of the responsibility for this vulnerability should fall on the Solidity language designer, which may improve the corrective measures for such cases. I disagree with the sole responsibility of poorly written contract code, even if the contract code is written completely according to the language documentation, there will still be such a vulnerability. A strange request for help While writing this article, I stumbled upon an interesting point about the DAO reward account: Why did the attacker collect the rewards, who deposited the money into the reward account? Why did the withdrawRewardFor function keep running during the DAO attack? Let’s look at the DAO’s reward address. The DAO’s accounting information comes from Slockit. The address is: 0xd2e16a20dd7b1ae54fb0312209784478d069c7b0 (https://github.com/slockit/DAO/wiki/Understanding-the-DAO-accounting). Take a look at its transaction records: https://etherscan.io/txsInternal?a=0xd2e16a20dd7b1ae54fb0312209784478d069c7b0&&zero=true&p=220 You can see that there are 200 pages of 0.00000002 ether transferred to 0xf835a0247b0063c04ef22006ebe57c5f11977cc4 and 0xc0ee9db1a9e07ca63e4ff0d5fb6f86bf68d47b89 , which is the attacker’s malicious contract. But look at where these coins came from, the account transaction records on the last page: a single money transaction record, sending 0.001 Ethereum from the address 0xe76563eb8413ede9b4a1a3c1f3280a95c4b60a33, at the end of the creation of the DAO. The same address later invested in The DAO and holds DigixDAO tokens. My theory is: he was probably a newbie who wanted to invest in the DAO crowdsale, tried to buy programmatically, and failed. But how could he get the main DAO addresses wrong? They are on the homepage of daohub.org, and the reward address appears in a remote corner of the DAO wiki. So why send money to this address? Just someone trying to play a joke on the DAO structure to see how they react? What's even more interesting is that searching for this account, you can find a post on an Ethereum forum where he seeks help with the Javascript API to send Ethereum. This account has never logged in since posting this post, and this is their only post https://forum.ethereum.org/discussion/7109/web3-eth-gettransaction-returned-null . This may be an interesting glimpse into blockchain history, and we will never know the answer. If you are the holder of this account, I would like to know your thoughts at the time. The attacker is not alone Looking at my previous post, the reason why I was able to analyze the attacker's path so quickly was because I was building such an attack myself before. A week ago I received an email from Emin Gün Sirer:
I spent a few hours analyzing the DAO for this vulnerability. Over the weekend, I spent a few hours troubleshooting several potential vulnerabilities in V1.1, and Sirer replied to me:
So I spent a few hours analyzing splitDAO, and I figured out that there was no chance of triggering the recursive send vulnerability. I simply said that it was unlikely. I cited timing issues, and that it was impossible to trigger the recursive send either from within splitDAO or when creating the TokenProxy. I demonstrated some possible exploit implementations, and then went back to work. There were other people on Sirer's team looking at this issue, and when we dug deeper, it became interesting that the attackers weren't the only ones who knew how to trigger this vulnerability. The DAO incident was inevitable. I wrote this series of articles out of curiosity rather than trying to prove it, but it gives me a little hope about the power of public scrutiny, and we are not too surprised by it. A short absence Thanks for reading this series of posts from me, I hope they were a little enlightening. There’s still a lot to do: We still need to reconstruct a complete blockchain picture of the attack, including where the hacker’s tokens ended up and why. We need to search public test networks to see if the hacker has tested the attack there. We need to carefully decompile and reconstruct his original Solidity code for documentation and analysis. We should learn from our experience and use best practices (a few ideas: slowly scale up these contracts, stop using calling constructs in Solidity), and as a community we should chart a course forward and decide whether to fork. Finally, I want to share that interest in smart contract technology is stronger than ever right now, and this is a great opportunity to showcase a stronger, more guided, more organized, and more principled community than we were last week. I'd like to thank this hacker for spending countless hours developing and testing this exploit. Hats off to you for beating us this time. I'm sure you won't be so lucky next time. |
<<: Exclusive interview with Bloq CEO Jeff Garzik: Greed is the cause of The DAO tragedy
The nose is a very important one among the five f...
Many people go fishing for a big meal. If they ca...
Rage Comment : Before fully understanding the imp...
Everyone is unique, everyone has their own living...
The tide of Internet finance is changing the exis...
As the saying goes, "Men are afraid of choos...
Author: GTong Blockchain, the company behind the ...
People with crooked noses have bad intentions. In...
According to a July 17 report from Bloomberg, Di ...
A new Deutsche Bank research paper suggests that ...
According to BlockBeats, Chinese mining machine m...
Does eyelid twitching mean something is about to ...
What does a mole on a man’s foot mean? As we all ...
On the road to making money, you must be careful ...
Being poor is a situation that a person will do e...