Six years of waiting: how MakerDAO was hacked once again

Six years of waiting: how MakerDAO was hacked once again

The attacker needed just 0.1 ETH, withdrawn from Tornado Cash, to launch the attack, and after the theft the 200 ETH began flowing back into the same service.

Oct 9, 2026

These 200 ETH consisted of four lots of 50 ETH that the contract had won in MakerDAO auctions with zero bids during the «Black Thursday» liquidations of March 2020, but had never finally claimed.

One of the old contract's unprotected functions did not check who was calling it. This allowed the attacker to hand a contract under their control full control over this contract's account in the MakerDAO system. In a single transaction, the attacker settled the old auctions and withdrew the collateral held there.

Key transactions (times in UTC)

TimeTXIDDescription
2026-10-06 05:57:230xd4c0b18a058e7f0a12855f30174f4cb1e973c15c886063deab97e1a21f048ac6Withdrawal of 0.1 ETH from Tornado Cash. This was the only funding this account received.
2026-10-06 06:12:110x4a587af4213cc345c4c109fbf5fec46f9643183f91a9edf60305380f31ad1167Deployment of the attack contract used to exploit the vulnerability.
2026-10-06 06:13:110xbb6940f7c2a1e68cafbae7bb9b94d09af9af06ec3a114f6996f2cab993f3a88cThe attack transaction. The attack contract obtained delegated permissions through the keeper function 0x8804d1de, settled auctions 1457–1460 and sent 200 ETH to the attacker's externally owned account (EOA).
2026-10-06 06:19:350xb8ecff9c129d8d9331c85558eaeddff29a38c4c16b71a40986acbf8f6febefebThe first of nine 10 ETH deposits into the Tornado Cash router.

Key addresses

AddressRole
0x01EB957E5C7DcDDD60F3C875956cCc6fb9BdA5FAThe attacker's externally owned account (EOA).
0xEc997d2aD033277913d6002277353368E8321dcFThe attack contract.
0xf09a13072Ed939B79Bc25B66AA3a836ea6DCC170The malicious adapter module, which still holds delegated permissions over the victim's account in Vat.
0x9c05a05893Ada984FC20D0DA0c046De5Cc0e8273The vulnerable contract (old auction keeper, AdminUpgradeabilityProxy).
0x68399ed8aa33C5b43F863EE6782de492006A5546The vulnerable contract implementation (source code not verified).
0xaDC374E77b89Af0B8B39F7c47E8e0A37B6DaF073The keeper's owner and operator in March 2020.
0xd8a04F5412223F513DC55F839574430f5EC15531MakerDAO ETH-A Flipper, a decommissioned auction mechanism for selling collateral.
0x35D1b3F3D7966A1DFe207aa4514C12a259A0492BMakerDAO Vat, the core accounting module.
0x2F0b23f53734252Bda2277357e97e1517d6B042AMakerDAO ETH-A GemJoin, the exit point for the collateral asset.
0xd90e2f925DA726b50C4Ed8D0Fb90Ad053324F31bThe Tornado Cash router, the address that received the stolen funds.
0x12D66f87A04A9E220743712cE6d9bB1B5616B8FcThe Tornado Cash 0.1 ETH pool, the source of the attacker's initial funding.

How the attack unfolded

Module deployment

The exploit began with the attack contract creating an adapter module at 0xf09a13072Ed939B79Bc25B66AA3a836ea6DCC170. On creation, the module received the following parameters: the Flipper, Vat, ETH-A GemJoin, the collateral type (ilk), WETH, a payout address and four auction IDs.

Handing over permissions

The attack contract called the keeper function 0x8804d1de and passed it the newly created adapter at 0xf09. The keeper then called the module's drip() and vat() functions, checked through Vat.can() whether permissions had already been granted to it, and called Vat.hope(). This gave the module unlimited permissions to manage the keeper's account in the Vat system.

The callback

The keeper then called the attack contract's join() function. This handed control to code written by the attacker at a moment when the new permissions were already in effect.

Settling the auctions

Inside the callback, the module called the deal(id) function of the decommissioned ETH-A Flipper for auctions 1457, 1458, 1459 and 1460. In March 2020, the keeper had originally won each of the four 50 ETH lots with a zero bid, but had never settled the deals. Once the auctions were settled, 200 ETH was credited as ETH-A collateral to the keeper's account in Vat.

Withdrawing the funds

The module read the keeper's collateral balance and used the permissions it had obtained to move all 200 ETH from the keeper's account in Vat to its own address with the flux() function. It then withdrew the collateral through ETH-A GemJoin as 200 WETH and sent the WETH to the attack contract.

What lies at the core of the vulnerability?

The root cause of the attack was the lack of access control on the function 0x8804d1de in the keeper implementation at 0x68399ed8aa33C5b43F863EE6782de492006A5546.

All of the contract's other privileged functions, including the keeper's wrappers for the hope, flux and move operations, first run a ds-auth check and reject unauthorized calls with the error ds-auth-unauthorized.

The function 0x8804d1de, by contrast, accepts any address as an adapter, calls Vat.hope() for that address if the keeper has not yet granted it permissions, and then calls join() on the same address.

Because the keeper then called join(), the attacker's module was able to run its own code at a moment when these permissions were already in effect.

The four unused lots from 2020 made this vulnerability profitable for the attacker. Anyone can call the deal function for an auction.

DeFiTuna taught to round: a logic error cost $570 thousand in USDCJul 20, 2026Read more

Movement of funds

The attacker received all 200 ETH directly within the transaction used to exploit the vulnerability. At 06:19:35 UTC, just six minutes later, the attacker began moving the funds into Tornado Cash, sending them as 20 deposits of 10 ETH.

#Hack#Ethereum#Cryptomixer#DeFi
200 ETH waited six years: the story of the attack on an old MakerDAO contract