Unauthorized Vault Approval Drain
Exploit Transactions
0x8421c96c1cafa451e025c00706599ef82780bdc0db7d17b6263511a420e0cf20Victim Addresses
0xcfe0de4a50c80b434092f87e106dfa40b71a5563Base0x49876a20bb86714e98a7e4d0a33d85a4011b3455BaseLoss Breakdown
Similar Incidents
Aerodrome V6 Unauthorized Reward Harvest via Forged Depositor Assignment
32%LeetSwap Base Pair Drain
31%USDC drain via unchecked Uniswap V3-style callback
31%MoltEVM Fake Spawner Drain
30%CAROL Reward Inflation Drain
29%ULME Approval Drain Exploit
29%Root Cause Analysis
Unauthorized Vault Approval Drain
1. Incident Overview TL;DR
On Base block 30655996, attacker EOA 0x2a49c6fd18bd111d51c4fffa6559be1d950b8eff called helper contract 0x7ee23c81995fe7992721ac14b3af522718b63f8f, which in turn interacted with BentoBox-derived vault 0xcfE0DE4A50C80B434092f87e106DFA40b71A5563. In a single transaction, the helper self-registered as a protocol, forged master-contract approval over victim account 0x49876a20bb86714e98a7e4d0a33d85a4011b3455 without any victim signature, withdrew the victim's entire RICE vault balance, liquidated the stolen RICE through public AMM liquidity, and transferred 34522914219203665619 wei WETH to the attacker EOA.
The root cause is an authorization failure in the deployed setMasterContractApproval(address,address,bool,uint8,bytes32,bytes32) implementation. The vault lets an arbitrary caller write masterContractApproved[masterContract][user] = true for a third-party victim without enforcing the normal BentoBox checks that should require either a valid user signature or a tightly constrained direct-call path.
2. Key Background
The victim contract follows the BentoBox and MasterContractManager pattern. Helper contracts are mapped through masterContractOf[msg.sender], and privileged helper actions such as withdraw(address,address,address,uint256,uint256) rely on whether masterContractApproved[masterContract][from] is set for the helper and victim pair.
This pattern is safe only when approval state changes are authenticated. In the Sushi reference implementation, setMasterContractApproval requires either a valid user signature or, in the no-signature branch, a narrow direct-call path where user == msg.sender, the caller is not already a clone, and the helper has been whitelisted. That design prevents an arbitrary attacker helper from self-authorizing against unrelated victims.
The Base vault at 0xcfE0DE4A50C80B434092f87e106DFA40b71A5563 also exposes registerProtocol(). That function is public and allows a caller contract to set masterContractOf[msg.sender] = msg.sender. On its own, public self-registration is not enough to steal funds. Combined with a broken approval setter, it becomes the missing piece that makes the later withdraw authorization gate pass.
3. Vulnerability Analysis & Root Cause Summary
This is an ATTACK-class access-control failure, not a pricing issue or benign MEV path. The critical invariant is: only the user whose funds are at risk, or a valid signature path on that user's behalf, may enable a helper contract to move that user's vault balance.
The deployed vault breaks that invariant in its approval setter. The exploit transaction shows the helper calling setMasterContractApproval(victim, helper, true, 0, 0, 0) and succeeding even though the victim did not send the transaction, no signature material was supplied, and the helper remained unwhitelisted. Once that unauthorized bit is written, the BentoBox-style withdraw gate sees masterContractOf[helper] == helper and masterContractApproved[helper][victim] == true, so the helper is treated as an authorized operator for the victim.
The exploit therefore reduces to a deterministic three-step chain: self-register helper, forge approval, then withdraw and liquidate victim assets. No privileged role, private key compromise, or attacker-specific artifact is required.
4. Detailed Root Cause Analysis
The relevant pre-state at block 30655995 is directly observable on-chain:
masterContractOf(0x7ee23c81995fe7992721ac14b3af522718b63f8f) = 0x0000000000000000000000000000000000000000
masterContractApproved(0x7ee23c81995fe7992721ac14b3af522718b63f8f, 0x49876a20bb86714e98a7e4d0a33d85a4011b3455) = false
balanceOf(RICE, 0x49876a20bb86714e98a7e4d0a33d85a4011b3455) = 22189176505973791717313474
Those pre-state facts match the collector's seed state and establish that the victim still held RICE shares and the attacker helper had no prior registration or approval.
The exploit transaction then executes the following vault calls in order:
0xcfE0DE4A50C80B434092f87e106DFA40b71A5563::registerProtocol()
0xcfE0DE4A50C80B434092f87e106DFA40b71A5563::setMasterContractApproval(
0x49876a20bB86714e98A7E4d0a33d85a4011b3455,
0x7ee23c81995fE7992721ac14B3AF522718b63f8F,
true,
0,
0x00...00,
0x00...00
)
0xcfE0DE4A50C80B434092f87e106DFA40b71A5563::withdraw(
0xf501E4c51dBd89B95de24b9D53778Ff97934cd9c,
0x49876a20bB86714e98A7E4d0a33d85a4011b3455,
0x7ee23c81995fE7992721ac14B3AF522718b63f8F,
22189176505973791717313474,
22189176505973791717313474
)
The decisive breakpoint is the second call. It proves the deployed approval setter does not require a victim-authenticated path before writing authorization state. The helper contract is not whitelisted, yet the call succeeds with v = 0, r = 0, and s = 0. That is incompatible with the intended BentoBox control flow and directly explains why the later withdraw gate accepts the helper.
Post-state checks at block 30655996 confirm the effect of that broken path:
masterContractOf(0x7ee23c81995fe7992721ac14b3af522718b63f8f) = 0x7ee23c81995fe7992721ac14b3af522718b63f8f
masterContractApproved(0x7ee23c81995fe7992721ac14b3af522718b63f8f, 0x49876a20bb86714e98a7e4d0a33d85a4011b3455) = true
whitelistedMasterContracts(0x7ee23c81995fe7992721ac14b3af522718b63f8f) = false
balanceOf(RICE, 0x49876a20bb86714e98a7e4d0a33d85a4011b3455) = 0
Because the helper is now both self-registered and falsely approved for the victim, withdraw transfers the victim's entire RICE position out of the vault. The trace emits:
emit Transfer(
from: 0xcfE0DE4A50C80B434092f87e106DFA40b71A5563,
to: 0x7ee23c81995fE7992721ac14B3AF522718b63f8F,
value: 22189176505973791717313474
)
That transfer is the concrete loss event. Everything after it is public-market liquidation of stolen assets rather than a separate vulnerability.
5. Adversary Flow Analysis
The attacker flow is a single permissionless transaction:
- The attacker EOA submits a call to helper contract
0x7ee23c81995fe7992721ac14b3af522718b63f8f. - The helper calls
registerProtocol()on the vault, makingmasterContractOf[helper] = helper. - The helper calls
setMasterContractApproval(victim, helper, true, 0, 0, 0)and successfully flips the approval bit for the victim despite having no victim signature and no whitelist status. - The helper immediately calls
withdraw(RICE, victim, helper, 22189176505973791717313474, 22189176505973791717313474)and drains the victim's full vault share balance. - The helper swaps the stolen RICE through public liquidity:
RICE/USDT pool swap on 0xa0213b570DFF35a8C826334472e23B9A8A94ef3b
USDT/USDC/WETH routing through router 0xBE6D8f0d05cC4be24d5167a3eF062215bE6D18a5
final WETH transfer to attacker EOA: 34522914219203665619
The trace ends with:
emit Transfer(
from: 0x7ee23c81995fE7992721ac14B3AF522718b63f8F,
to: 0x2a49c6FD18BD111d51C4ffFA6559bE1d950B8Eff,
value: 34522914219203665619
)
This confirms that the exploit predicate is straightforward attacker profit from unauthorized victim withdrawal.
6. Impact & Losses
The directly observed loss is the victim's full RICE vault balance:
{
"token_symbol": "RICE",
"amount": "22189176505973791717313474",
"decimal": 18
}
The victim holder 0x49876a20bb86714e98a7e4d0a33d85a4011b3455 was reduced from 22189176505973791717313474 RICE shares to 0 in one transaction. The attacker EOA received 34522914219203665619 wei WETH, while the collector's balance diff shows 888338673200281558 wei of native ETH fees, for a net ETH-denominated gain of 33634575546003384061 wei.
Any account with shares in this vault was exposed to the same path. The exploit requires only a fresh helper contract, a victim with a positive vault balance, and public liquidity for liquidation.
7. References
- Seed transaction:
0x8421c96c1cafa451e025c00706599ef82780bdc0db7d17b6263511a420e0cf20on Base block30655996 - Victim vault:
0xcfE0DE4A50C80B434092f87e106DFA40b71A5563 - Victim share holder:
0x49876a20bb86714e98a7e4d0a33d85a4011b3455 - Attacker EOA:
0x2a49c6fd18bd111d51c4fffa6559be1d950b8eff - Attacker helper:
0x7ee23c81995fe7992721ac14b3af522718b63f8f - Stolen token: RICE
0xf501E4c51dBd89B95de24b9D53778Ff97934cd9c - Profit token: WETH
0x4200000000000000000000000000000000000006 - Primary evidence: collector seed metadata, trace, and balance diff for the incident transaction
- Reference design: Sushi BentoBox
MasterContractManager.sol, used only to contrast the expected approval checks with the deployed broken behavior