Root Cause
The vault held yield-generating positions in Aave V3 on Base, with assets held as Aave V3 receipt tokens (aBaswstETH). A Safe multisig (reportedly 3-of-7 signers) controlled which addresses were permitted to call the vault's withdraw() function via an on-chain whitelist. The root cause was unauthorized modification of that whitelist: an attacker-controlled contract was added to the Safe's permitted-withdrawer list with no time-lock or governance delay, enabling the attacker to immediately drain the vault through Aave V3's standard redemption path.
// Illustrative vault pseudocode (inferred from on-chain behavior)
mapping(address => bool) public whitelist;
modifier onlyWhitelisted() {
require(whitelist[msg.sender], "Not whitelisted");
_;
}
// No time-lock or rate-limit on whitelist changes
function setWhitelisted(address addr, bool val) external onlyMultisig {
whitelist[addr] = val; // Instant, no delay enforced
}
function withdraw(uint256 amount) external onlyWhitelisted {
// Redeems aBaswstETH via Aave V3, sends wstETH to msg.sender
aave.withdraw(WSTETH, amount, msg.sender);
}
Whether the multisig was compromised via phishing, a stolen signing key, or insider action remains unconfirmed. The double add/remove/re-add sequence within one minute suggests the signers were either tricked or that key material was directly compromised.
Attack Steps
| Step | On-Chain Event | Time (UTC) |
|---|---|---|
| 1 | Attacker deploys malicious withdrawal contract | ~08:50 |
| 2 | Safe multisig adds attacker contract to whitelist | 08:52 |
| 3 | Safe multisig removes attacker contract (1 min later) | 08:53 |
| 4 | Safe multisig re-adds attacker contract to whitelist | 08:53 |
| 5 | Attacker calls withdraw() six times via whitelisted contract | 08:52–09:11 |
| 6 | 1,783 aBaswstETH redeemed via Aave V3 for 1,783 wstETH | ~09:11 |
Total drain window: approximately 19 minutes.
Impact
- Direct loss: 1,783 wstETH ≈ $6,000,000 USD
- Remaining in vault at time of exploit: ~$31.7M
- Chain: Base (Ethereum L2)
- Aave V3 status: Not compromised; the lending protocol functioned as designed
- Vault operator: Anonymous; no public post-mortem published as of reporting
- Disclosure gap (per Immunefi): All seven Safe signers were unidentified, complicating whitehat response
Lessons for Auditors
- Time-lock whitelist changes. Administrative operations that govern who can drain a vault must enforce a minimum delay (48–72 hours minimum) via
TimelockControlleror equivalent. Instant multisig whitelist changes are high-risk regardless of threshold. - Rate-limit large withdrawals. Per-epoch caps prevent a single transaction or block from emptying an entire vault position.
- Require identified signers for high-TVL vaults. Anonymous multisigs above a meaningful TVL threshold should be treated as a critical finding. At minimum, require a subset of public or legally accountable signers.
- Separate whitelist management from yield logic. A dedicated governance contract with timelock for whitelist changes, distinct from the Aave integration layer, would limit blast radius.
- Real-time alerting on whitelist mutations. Any
setWhitelisted(addr, true)event adding a new address should trigger an immediate security alert.