MakerDAO Legacy Auction Keeper Incident Analysis

리서치 사고 분석
MakerDAO Legacy Auction Keeper Incident Analysis

Incident Summary

On October 6, 2026, at 06:13:11 UTC, an attacker drained 200 ETH ($542,982 at the time of the exploit) of ETH-A collateral from a legacy auction-keeper contract on Ethereum mainnet. The 200 ETH came from four 50 ETH lots that the keeper won with zero bids during MakerDAO's "Black Thursday" liquidations in March 2020 and never settled. An unauthenticated function on the keeper let the attacker give a contract it controlled full delegation over the keeper's MakerDAO Vat account. In a single transaction, the attacker settled the old auctions and withdrew the collateral.

Key Transactions (Times in UTC)

Timestamp Transaction / Explanation
2026-10-06 05:57:23 UTC 0xd4c0b18a058e7f0a12855f30174f4cb1e973c15c886063deab97e1a21f048ac6: Funding. Tornado Cash withdrawal of 0.1 ETH. This was the account's only funding.
2026-10-06 06:12:11 UTC 0x4a587af4213cc345c4c109fbf5fec46f9643183f91a9edf60305380f31ad1167: Deployment of the attack contract that was used in the exploit.
2026-10-06 06:13:11 UTC 0xbb6940f7c2a1e68cafbae7bb9b94d09af9af06ec3a114f6996f2cab993f3a88c: Exploit transaction. The attack contract obtained Vat delegation through keeper function 0x8804d1de, settled auctions 1457–1460, and sent 200 ETH to the attacker EOA.
2026-10-06 06:19:35 UTC 0xb8ecff9c129d8d9331c85558eaeddff29a38c4c16b71a40986acbf8f6febefeb: First of nine 10 ETH deposits into the Tornado Cash router.

Key Addresses

Address Label
0x01EB957E5C7DcDDD60F3C875956cCc6fb9BdA5FA Attacker EOA
0xEc997d2aD033277913d6002277353368E8321dcF Attack contract
0xf09a13072Ed939B79Bc25B66AA3a836ea6DCC170 Malicious adapter module, still holds Vat delegation over the victim
0x9c05a05893Ada984FC20D0DA0c046De5Cc0e8273 Vulnerable contract (legacy auction keeper, AdminUpgradeabilityProxy)
0x68399ed8aa33C5b43F863EE6782de492006A5546 Vulnerable implementation (source not verified)
0xaDC374E77b89Af0B8B39F7c47E8e0A37B6DaF073 Owner of the keeper and its operator in March 2020
0xd8a04F5412223F513DC55F839574430f5EC15531 MakerDAO ETH-A Flipper (retired collateral auction house)
0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B MakerDAO Vat (core accounting)
0x2F0b23f53734252Bda2277357e97e1517d6B042A MakerDAO ETH-A GemJoin (collateral exit point)
0xd90e2f925DA726b50C4Ed8D0Fb90Ad053324F31b Tornado Cash router (deposit destination for the proceeds)
0x12D66f87A04A9E220743712cE6d9bB1B5616B8Fc Tornado Cash 0.1 ETH pool (source of the attacker's funding)

Attack Flow

  1. Module deployment. The exploit transaction began with the attack contract creating an adapter module at 0xf09a13072Ed939B79Bc25B66AA3a836ea6DCC170. The constructor received: the Flipper, Vat, ETH-A GemJoin, ilk, WETH, the payout address, and the four auction IDs.
  2. Delegation. The attack contract called keeper function 0x8804d1de and passed the newly created adapater at 0xf09. The keeper then called drip() and vat() on the module, checked whether it had already delegated to it via Vat.can(), and called Vat.hope(). This gave the module unrestricted authority over the keeper's Vat account. maker1
  3. Callback. The keeper then called the attacker's join() function, which handed control to attacker-written code while the new delegation was active.
  4. Settlement. Inside the callback, the module called deal(id) on the retired ETH-A Flipper for auctions 1457, 1458, 1459 and 1460. The keeper had originally won each 50 ETH lot with a zero bid in March 2020 but never settled. Settling the auctions credited 200 ETH of ETH-A collateral to the keeper's Vat account. maker7
  5. Extraction. The module read the keeper's collateral balance and used its delegation to flux() all 200 ETH from the keeper's Vat account to itself. It then exited the collateral through the ETH-A GemJoin as 200 WETH and forwarded the WETH to the attack contract. maker3

Vulnerability

The root cause was a missing access control on function 0x8804d1de in keeper implementation 0x68399ed8aa33C5b43F863EE6782de492006A5546. Every other privileged function in the contract, including the keeper's hope, flux and move wrappers, runs a ds-auth check first and reverts with ds-auth-unauthorized for unauthorized callers. Function 0x8804d1de accepts any address as an adapter, calls Vat.hope on that address if the keeper has not already delegated to it, and then calls join() on the same address. In the Vat, hope gives the delegated address full control over the account's collateral and internal DAI. Because the keeper then called join(), the attacker's module ran its own code while that authority was active. The function never calls nope, so the delegation remained in place after the transaction. The keeper's four unsettled 2020 lots made the flaw profitable. Anyone can call deal on an auction. maker6

Fund Flow

The attacker received the full 200 ETH directly in the exploit transaction. At 06:19:35 UTC, six minutes later, the attacker began sending the funds to Tornado Cash in 20 x 10 ETH deposits. maker5

To keep up to date on the latest incident alerts and statistics, follow @certikalert on X, explore our incident dashboard, or read our latest analysis on certik.com.