Security behind the MakerDAO governance contract upgrade

On May 07, 2019, Beijing time, blockchain security company Zeppelin issued a security warning to DeFi star project MakerDAO on Ethereum, claiming that there is a security breach in its governance contract, and hopes that users who have locked the voting will unlock MKR as soon as possible. . MakerDAO's developer Maker also confirmed the existence of the vulnerability and launched a new governance contract, claiming that the vulnerability has been fixed.

After the security threat was exposed, PeckShield tracked the transfer of MKR tokens and issued a warning to the community several times, calling on MKR token holders to immediately transfer the MKR tokens of the old contract. Up to now, most of the MKR tokens have been transferred, and there are 2,463 MKR tokens (worth about $1.28 million) in the old governance contract to be transferred.

On May 07, the independent research by PeckShield found that the existence of the vulnerability (we named itchy DAO), specifically: due to some defect in the voting mechanism (vote(bytes32)) implemented by the governance contract, Vote for a slate that doesn't exist yet (but includes a proposal that is voting). After the user votes, the attacker can maliciously call free() to exit, to reduce the legitimate votes of the valid proposal, and simultaneously lock the voter's MKR token.

On May 8th the following day, PeckShield Emergency and Maker Company synchronized the details of the vulnerability. On the morning of May 10th, MakerDAO released the new version of the contract. Zeppelin and PeckShield also independently completed their own audit of the new contract and determined that the new version fixes the vulnerability.

Here we publish the details of the vulnerability and the attack method, and hope that other DApps that reference this third-party library contract can be fixed as soon as possible.

detail

In the design of MakerDAO, users can participate in their governance mechanism by voting. For details, please refer to DAO's FAQ.

Here are the details about itchy DAO. Users can lock and vote for MKR on their hands with lock / free:

After lock locks MKR, one or more proposals (address arrays) can be voted on:

Notice that there are two vote functions, the difference between the two is different (address array and byte32),

The vote(address[] yays) will eventually call vote(bytes32 slate), whose general logic is as shown below:

To put it simply, the two votes are the same, and finally call addWeight to put the locked ticket into the corresponding proposal:

Unfortunately, due to the mistakes in the design of the contract, the attacker has the opportunity to maliciously manipulate the voting result through a series of actions, so that the locked MKR cannot be taken out.

Here we assume that there is a hacker who has never voted to start an attack:

  • Call lock() to lock the MKR, at which point deposits[msg.sender] will be credited to the locked amount.
  • At this point, the hacker can pre-calculate the proposal to be attacked and pre-calculate the hash value, which is used as the pass parameter of step 3, because slate is actually just sha3 of the address array. It should be noted here that the selected target combination must not exist in slates[] (otherwise the attack will fail), and the hacker can also propose a new proposal to join the combination calculation, so that the combination must not exist.
  • Call vote(bytes32 slate), because slate is actually only the sha3 of the address array, the hacker can pass the proposal before the line to pre-calculate the proposal to be attacked. At this time, because votes[msg.sender] has not been assigned, subWeight() will return directly. Next, the s3 that the hacker passed in will be saved to votes[msg.sender], then call addWeight(). From the code above we can see that addWeight() gets the proposal array through slates[slate], and the slates[slate] gets the same unassigned initial array, so the for loop won't execute (because of yays. Length = 0)

  • Call etch() to pass in the target proposal array. Note that etch() and both vote() functions are public, so the outside can be called at will. At this point slates[hash] will be stored in the corresponding array of proposals.
  • Call free() to unlock the lock. This will be divided into the following two steps:
  • Deposits[msg.sender] = sub(deposits[msg.sender], wad) unlocks the hacker's lock in 1.
  • subWeight(wad, votes[msg.sender]) deducted the number of hackers from the corresponding proposal, but from the beginning to the end the attackers did not actually vote for them.
  • From the above analysis, we understand that hackers can cause the following possible effects through this attack:

    1. Malicious manipulation of voting results

    Second, because the hacker pre-deducted part of the votes, the real voter may not be able to unlock the lock

    Timeline

    PeckShield is the world's leading provider of blockchain data and security services. Business and media cooperation (including smart contract audit requirements), please contact us via Telegram, Twitter or email (contact@peckshield.com).