# RnsPay: arbitrary external call drains victim USDC allowance

RnsPay's pay() executed attacker-supplied calldata against an arbitrary target, letting a fake token pass balance checks and drain 1,050 USDC from a victim's approval.

- Payout: Real-world hack
- Severity: high
- Program: RnsPay
- Credited: DeFiHackLabs
- Disclosed: 2025-03-06
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2025-03/RnsPay_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20250306-rnspay
- Tags: domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm tech:solidity technique:source-review vuln:access-control vuln:business-logic

**Target:** Solidity payment contract pay() with user-supplied dexRouterContractAddress and dexRouterFeedData
**Root cause:** pay() trusts caller-supplied payTokenContractAddress and dexRouterContractAddress, using a fake ERC20 for balance/allowance checks while forwarding arbitrary dexRouterFeedData as a call to the real USDC contract.
**Steps:**
1. Deploy FakeToken returning 1 for balanceOf and true for transferFrom/approve.
2. Build Payment with payTokenContractAddress=receiptTokenContractAddress=FakeToken, dexRouterContractAddress=USDC, dexRouterFeedData=`transferFrom(victim, attacker, 1050000000)`.
3. Call `IRnsPay(RNS_PAY).pay(payment)`; RnsPay's fake-token balance checks pass and it executes the USDC transferFrom as msg.sender.
4. Approve Uniswap V2 Router and `swapExactTokensForETH(1050000000, 0, [USDC,WETH], attacker, block.timestamp)`.
5. Forward ETH to attacker.
**How found:** Recognized that pay() accepts an arbitrary exchange target and calldata, and that token balance checks could be satisfied with a fake ERC20 while the real call target is USDC.
**Exploitation:** Attacker needs a victim who approved RnsPay to spend USDC. By supplying a fake token for both pay and receipt token and pointing dexRouterContractAddress at USDC with transferFrom calldata, RnsPay itself pulls the victim's USDC into the attack contract, which swaps to ETH via Uniswap V2.
**Insight:** When a contract takes both a token address and a call target/calldata from the caller, decouple the accounting token from the execution target: a fake token can satisfy balance checks while the calldata hits a real token's transferFrom using the contract's own approvals.
