# An attacker can steal all tokens of users that use FeeWrapper

FeeWrapper's public rubicall lets anyone make it call any contract, so an attacker cancels a victim's offer and transfers the refunded tokens to themselves.

- Payout: Contest reward
- Severity: high
- Program: Rubicon
- Credited: CryptoCraze
- Disclosed: 2025-02-17
- Write-up quality: 8/10
- Original: https://github.com/code-423n4/2023-04-rubicon-findings/issues/767
- Library page: https://bugbountylibrary.com/r/c4%3A2023-04-rubicon%3AH-09
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:access-control vuln:accounting-error vuln:business-logic

**Target:** FeeWrapper periphery contract / RubiconMarket offers
**Root cause:** FeeWrapper.rubicall is publicly callable and forwards arbitrary selector/args to any target, so all calls share FeeWrapper's msg.sender context and anyone can invoke cancel/transfer on its behalf.
**Steps:**
1. Victim approves 3rd-party protocol, which approves FeeWrapper and calls rubicall to create an offer on RubiconMarket; offer is owned by FeeWrapper.
2. Attacker calls `feeWrapper.rubicall({selector: cancel(id), args: victimOfferId, target: rubiconMarket})` to cancel victim's offer, refunding tokens to FeeWrapper.
3. Attacker calls `feeWrapper.rubicall({selector: transfer(attacker, bal), args: ..., target: testCoin})` to move FeeWrapper's token balance to attacker.
4. Victim balance becomes 0; attacker receives all tokens.
**How found:** Noticed that calls routed through FeeWrapper make msg.sender = FeeWrapper, so offers are owned by the wrapper and caller identity is indistinguishable; then observed rubicall accepts arbitrary target/selector/args, enabling impersonation of any user.
**Exploitation:** No capital needed: attacker directly invokes the permissionless rubicall twice — once to cancel the victim's offer (refund lands in FeeWrapper) and once to transfer the ERC20 balance out to the attacker. Works for any user/3rd-party that granted FeeWrapper an allowance or whose offers are owned by FeeWrapper.
**Escalation:** Same primitive extends to RubiconRouter (cancelForETH) once FeeWrapper gains a payable fallback, and to transferFrom draining any allowance granted to FeeWrapper.
**Insight:** When a shared helper contract is the msg.sender for downstream protocols, its public entrypoint becomes an identity-confusion primitive: check whether arbitrary target+selector lets you replay privileged actions (cancel, transferFrom) against balances/allowances held by the helper.
