- Type: Exploit
- Total lost: ~4MM
- Category: Access Control
- Exploited contracts:
- Attack transactions:
- Attacker addresses:
- Attack Block: 13155350
- Date: Sep 03, 2021
- Reproduce:
forge test --match-contract Exploit_DAOMaker -vvv
- Call
init
to set yourself as owner - Call
emergencyExit
to withdraw tokens
On Sept 03, 2021 an attacker stole over 4MM USD in various tokens from an DAOMaker.
The attacker called init
, which is not access-controlled, and then called emergencyExit
withdrawing the tokens held.
The vulnerability is hard to detect as contracts were not verified, thus the source code is not readily available.
Nevertheless, we can see the first attack tx calls an init method with a sighash 84304ad7
.
The exploited contract is simply a universal-proxy-like, which delegates call to an implementation that holds the actual upgrade logic. This implementation contract did not prevent an arbitrary address to call its init
method.
The init
method sets as owner
anyone who calls it. You can check the decompilation and look for the unknown84304ad7
method, as the decompiler calls it. Look at the bottom, you will see owner = caller
.
def unknown84304ad7() payable:
require calldata.size - 4 >= 128
require cd <= 4294967296
require cd <= calldata.size
require ('cd', 36).length <= 4294967296 and cd * ('cd', 36).length) + 36 <= calldata.size
require cd <= 4294967296
require cd <= calldata.size
require ('cd', 68).length <= 4294967296 and cd * ('cd', 68).length) + 36 <= calldata.size
...
log OwnershipTransferred(
address previousOwner=owner,
address newOwner=caller)
owner = caller
We can be sure this transaction triggered because there is an event in the event list. See that the first one sets it from zero to an OK address, then after a while from the OK address to the attacker's.
This allowed the attacker to call emergencyExit
(sighash: a441d067
) which is onlyOwner
protected.
In his twitter thread, Mudit Gupta suggests that the attacker was using a browser wallet as the calls where made separeterly without a contract and the browser wallet built-in swap was used.
Also, the contract attacked was not verified. The fact that the attacker used only an EOA to perform the attack on a non verified contract suggests that maybe the attacker had insider-knowledge of this vulnerability.
initialize
functions should always be protected so they can be called only once