# All tokens can be stolen from VirtualAccount due to missing access modifier

VirtualAccount.payableCall lacked the requiresApprovedCaller modifier, letting anyone execute arbitrary calls and transfer out all ERC20/NFT assets held by the account.

- Payout: Contest reward
- Severity: high
- Program: Maia DAO
- Credited: 0xTheC0der
- Disclosed: 2023-11-29
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2023-09-maia-findings/issues/885
- Library page: https://bugbountylibrary.com/r/c4%3A2023-09-maia%3AH-01
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:access-control vuln:auth-bypass

**Target:** VirtualAccount.payableCall (Solidity contract)
**Root cause:** payableCall() was declared public payable without the requiresApprovedCaller modifier that the sibling call() function uses, so any external caller could make the contract execute arbitrary target calls.
**Steps:**
1. Deploy VirtualAccount for victim Alice with a local port.
2. Attacker builds `PayableCall[]` with target = token address and callData = `abi.encodeCall(ERC20.transfer, (attacker, balance))`.
3. Attacker calls `vAcc.payableCall(calls)` from an unauthorized address.
4. No modifier check fires; the contract executes transfer on its own behalf, moving tokens to attacker.
**How found:** Code review comparing payableCall against the sibling call() function, noticing call() carries requiresApprovedCaller while payableCall does not, then writing a Foundry PoC to confirm an unauthorized caller can trigger ERC20.transfer.
**Exploitation:** Attacker calls payableCall with a crafted PayableCall array targeting any ERC20/NFT the VirtualAccount holds, using transfer/transferFrom calldata to move assets to themselves; no approval or capital needed.
**Insight:** When a contract exposes multiple external-call entrypoints (call vs payableCall), diff their modifiers — a missing access modifier on one variant of the same primitive is a common high-severity gap.
