# Malicious actors can exploit user-approved allowances on VaultRouter to drain their ERC4626 tokens

VaultRouter's redeem/withdraw commands accept an arbitrary owner param, letting anyone spend a user's approved ERC4626 shares and redirect assets to themselves.

- Payout: Audit contest
- Severity: high
- Program: BakerFi
- Found by: 0xpiken
- Disclosed: 2025-02-26
- Write-up quality: 6/10
- Original: https://code4rena.com/reports/2024-12-bakerfi-invitational#h-07
- Library page: https://bugbountylibrary.com/r/c4%3A2024-12-bakerfi-invitational%3AH-07
- Tags: area:web3 domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:access-control vuln:business-logic

**Target:** VaultRouter.execute ERC4626 redeem/withdraw commands
**Root cause:** _handleVaultRedeem and _handleVaultWithdraw read the owner address from calldata instead of forcing msg.sender, so any caller can redeem/withdraw on behalf of any user who approved the router.
**Steps:**
1. User approves VaultRouter to spend their ERC4626 vault shares.
2. Attacker builds commands array with ERC4626_VAULT_REDEEM encoding redeemVault(vault, shares, attackerReceiver, victimOwner).
3. Attacker calls `vaultRouter.execute(commands)` from any account.
4. Router pulls victim's shares via allowance, redeems to attacker's receiver address.
**How found:** Code review of the assembly calldata decoding in _handleVaultRedeem/_handleVaultWithdraw revealed owner is attacker-controlled rather than bound to msg.sender, combined with the router's share allowance model.
**Exploitation:** Attacker needs only a victim's standing approval to VaultRouter. They craft a single execute() call with ERC4626_VAULT_REDEEM (or ERC4626_VAULT_WITHDRAW) setting receiver to themselves and owner to the victim, draining all approved shares into underlying assets sent to the attacker.
**Insight:** In multicall/router contracts that pull tokens via allowance, any calldata field naming an 'owner'/'from' address is an authorization boundary — check whether it is bound to msg.sender or freely supplied by the caller.

