Root Cause
The keeper contract (implementation 0x68399ed8aa33C5b43F863EE6782de492006A5546) contained a withdrawal/exit function at selector 0x8804d1de that performed no caller validation:
// Reconstructed vulnerable pattern (function selector 0x8804d1de)
// Missing: auth modifier from ds-auth, or any require(msg.sender == owner)
function exit() external {
uint256 bal = address(this).balance;
payable(msg.sender).transfer(bal); // no access control
}
MakerDAO's own Dai Savings Rate and core contracts use the ds-auth library's auth modifier which gates privileged calls to a configurable authority. This third-party keeper's exit path was written without that modifier. The contract sat dormant for over six years without issue only because its balance was not widely known.
CertiK confirmed: "the root cause was a missing access control on function 0x8804d1de in keeper implementation 0x68399ed8aa33C5b43F863EE6782de492006A5546."
Why the Contract Held 200 ETH
During MakerDAO's Black Thursday liquidation cascade on March 12–13, 2020, the ETH gas price spiked so high that competing keeper bots were unable to submit bids. This particular keeper won four 50 ETH ETH-A collateral lots at zero bids, receiving the collateral for free. These winnings were never withdrawn and sat in the contract for over six years.
Attack Steps
| Step | Action |
|---|---|
| 1 | Attacker identifies dormant keeper contract holding 200 ETH on Ethereum mainnet |
| 2 | Calls function selector 0x8804d1de (exit) directly — no auth required |
| 3 | All 200 ETH transferred to attacker address instantly |
| 4 | Within 6 minutes: laundering begins via Tornado Cash, split into 10 ETH batches |
Impact
- Loss:
200 ETH ($538,000–$543,000 depending on price at exploit time) - Chain: Ethereum mainnet
- MakerDAO core: Unaffected — DAI peg, collateral vaults, and governance contracts functioned normally
- Laundering: Tornado Cash mixing started approximately 6 minutes post-exploit
Lessons for Auditors
- Dormant contracts are not safe contracts. A contract that has been sitting idle for years accumulates value (protocol fees, unclaimed rewards, unsettled positions) and becomes a high-reward target as its balance grows. All deployed contracts, regardless of age, must be considered in-scope.
- Apply access control to every state-mutating function, without exception. There is no safe default for a function without an explicit
auth,onlyOwner, or equivalent modifier — assume attackers will find it. - Audit third-party integrations, not just core protocol contracts. Keeper bots, liquidation bots, and off-chain automation contracts that hold balances or interact with protocol state should receive the same security scrutiny as the protocol itself.
- Investigate historical zero-bid or unusual settlement events. Contracts that benefited from edge-case conditions (zero-bid lots, Black Thursday-era artefacts) may hold unexpected on-chain value years later.