All incidents

FiberRouter Allowance Reuse Drain

Share
Nov 28, 2023 01:42 UTCAttackLoss: 59.01 USDCPending manual check1 exploit txWindow: Atomic
Estimated Impact
59.01 USDC
Label
Attack
Exploit Tx
1
Addresses
3
Attack Window
Atomic
Nov 28, 2023 01:42 UTC → Nov 28, 2023 01:42 UTC

Exploit Transactions

TX 1BSC
0x7260ad0e4769ae68f0a680356c63140353c18d7be1b86a8c4e99a0fc3b6842c1
Nov 28, 2023 01:42 UTCExplorer

Victim Addresses

0x4826e896e39dc96a8504588d21e9d44750435e2dBSC
0x6697fa48f7335f4d59655aa4910f517ec4109987BSC
0x4da35bf35504d77e5c5e9db6a35b76eb4479306aBSC

Loss Breakdown

59.01USDC

Similar Incidents

Root Cause Analysis

FiberRouter Allowance Reuse Drain

1. Incident Overview TL;DR

On BNB Smart Chain block 33874499, transaction 0x7260ad0e4769ae68f0a680356c63140353c18d7be1b86a8c4e99a0fc3b6842c1 exploited Fiber's public router 0x4826e896e39dc96a8504588d21e9d44750435e2d (FiberRouter). The adversary EOA 0x4D9c558660D8165F0C31566Fc861978f16181eaD deployed helper contract 0xfbE9B2307C0b1cf9601f927332b3a5942860A500, seeded FiberRouter with dust USDC, then called FiberRouter.swapAndCrossOneInch so that FiberRouter executed attacker-supplied calldata against the BSC USDC token 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d.

Because victim address 0x4da35bf35504d77e5c5e9db6a35b76eb4479306a had already approved FiberRouter to spend USDC, the raw call ran with msg.sender = FiberRouter and successfully executed USDC.transferFrom(victim, helper, 59012161810470474620). FundManager 0x6697fA48f7335F4D59655aA4910F517ec4109987 then pulled only 1 USDC from FiberRouter for the bridge leg, leaving the stolen balance with the attacker helper. The helper sold the stolen USDC through PancakeRouter and the sender EOA realized a net native profit of 254159737186739733 wei.

The root cause is a public arbitrary-call primitive in FiberRouter: swapAndCrossOneInch accepts an attacker-chosen swapRouter and attacker-chosen calldata, then _swapAndCrossOneInch performs address(swapRouter).call(_calldata) under FiberRouter's own approval context. That turns any pre-existing ERC20 allowance to FiberRouter into a permissionless theft opportunity.

2. Key Background

FiberRouter is Fiber's public swap-and-bridge entrypoint. FundManager trusts exactly one router address, and validator-confirmed on-chain reads at block 33874498 show:

  • FundManager.router() == 0x4826e896E39DC96A8504588D21e9D44750435e2D
  • FundManager.allowedTargets(USDC, 43114) == 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E
  • USDC.allowance(0x4da35bf35504d77e5c5e9db6a35b76eb4479306a, FiberRouter) was a live nonzero allowance
  • USDC.balanceOf(0x4da35bf35504d77e5c5e9db6a35b76eb4479306a) == 59012161810470474620

FundManager is relevant because its bridge flow pulls funds from FiberRouter rather than from the caller directly. The verified source shows swapToAddress is onlyRouter, checks that the target token is allowed for the target network, and then executes SafeAmount.safeTransferFrom(token, from, address(this), amount) where from is the router.

That trust model is safe only if FiberRouter can never be tricked into spending assets that were not intentionally sourced for the current call. In Fiber, that assumption is false because the router exposes a raw external call to an attacker-chosen target and attacker-chosen calldata.

3. Vulnerability Analysis & Root Cause Summary

This incident is an ATTACK, not an MEV-only liquidation or a privileged compromise. FiberRouter's swapAndCrossOneInch entrypoint lets any caller choose the swapRouter, fromToken, foundryToken, amountIn, amountCrossMin, and the raw calldata that FiberRouter will execute. The function transfers amountIn from the caller, approves the attacker-chosen router, approves FundManager for amountCrossMin, and then delegates actual execution to _swapAndCrossOneInch.

The code-level breakpoint is _swapAndCrossOneInch executing address(swapRouter).call(_calldata) with no target whitelist, no selector filtering, no binding between calldata and msg.sender, and no post-call accounting that proves the router only handled the caller's own assets. Because amountIn can be zero, the attacker does not need FiberRouter to pull any tokens from the attacker before the raw call. If a third party has already approved FiberRouter for a token, the attacker can point swapRouter at that token contract and encode a transferFrom that spends the victim's allowance.

FundManager's downstream checks do not prevent the theft. The only bridge-side requirement is that FiberRouter must hold and approve amountCrossMin of the chosen foundry token. The attacker satisfied that requirement by first sending 2271374 USDC units to FiberRouter through a dust Pancake swap, so the bridge leg consumed only 1 USDC while the stolen victim balance remained with the attacker.

The violated invariant is: a public router must only spend assets intentionally sourced from the current caller for the current operation, and must never let arbitrary calldata exercise unrelated third-party allowances already granted to the router address.

4. Detailed Root Cause Analysis

4.1 Victim Code Path

The verified FiberRouter source contains the vulnerable sequence:

function swapAndCrossOneInch(
    address swapRouter,
    uint256 amountIn,
    uint256 amountCrossMin,
    uint256 crossTargetNetwork,
    address crossTargetToken,
    address crossTargetAddress,
    uint256 swapBridgeAmount,
    bytes calldata _calldata,
    address fromToken,
    address foundryToken
) external {
    IERC20(fromToken).safeTransferFrom(msg.sender, address(this), amountIn);
    IERC20(fromToken).approve(swapRouter, amountIn);
    IERC20(foundryToken).approve(pool, amountCrossMin);
    _swapAndCrossOneInch(
        crossTargetAddress,
        swapRouter,
        amountCrossMin,
        crossTargetNetwork,
        crossTargetToken,
        _calldata,
        foundryToken
    );
}

function _swapAndCrossOneInch(
    address to,
    address swapRouter,
    uint256 amountCrossMin,
    uint256 crossTargetNetwork,
    address crossTargetToken,
    bytes memory _calldata,
    address foundryToken
) internal {
    (bool success, ) = address(swapRouter).call(_calldata);
    if (!success) revert("SWAP_FAILED");
    FundManager(pool).swapToAddress(
        foundryToken,
        amountCrossMin,
        crossTargetNetwork,
        crossTargetToken,
        to
    );
}

The verified FundManager source shows why the attacker only needed to seed the router with a tiny amount:

address public router;
mapping(address => mapping(uint256 => address)) public allowedTargets;

modifier onlyRouter() {
    require(msg.sender == router, "BP: Only router method");
    _;
}

function swapToAddress(
    address token,
    uint256 amount,
    uint256 targetNetwork,
    address targetToken,
    address targetAddress
) external onlyRouter returns (uint256) {
    return _swap(msg.sender, token, amount, targetNetwork, targetToken, targetAddress);
}

function _swap(
    address from,
    address token,
    uint256 amount,
    uint256 targetNetwork,
    address targetToken,
    address targetAddress
) internal returns (uint256) {
    require(allowedTargets[token][targetNetwork] == targetToken, "BP: target not allowed");
    amount = SafeAmount.safeTransferFrom(token, from, address(this), amount);
}

At block 33874498, the on-chain pre-state already satisfied every exploit precondition:

  • the victim holder had 59012161810470474620 USDC,
  • the victim holder had a live allowance to FiberRouter,
  • FundManager already trusted FiberRouter as router,
  • FundManager already allowed BSC USDC to bridge to Avalanche USDC (43114 -> 0xB97E...).

4.2 Exploit Execution

The collector trace records the complete exploit in a single adversary-crafted transaction:

0x4826...35e2D::swapAndCrossOneInch(
  USDC,
  0,
  1,
  43114,
  0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E,
  0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E,
  0,
  0x23b872dd...,
  USDC,
  USDC
)
...
BEP20TokenImplementation::transferFrom(
  0x4da35bf35504D77e5C5E9Db6a35B76eB4479306a,
  0xfbE9B2307C0b1cf9601f927332b3a5942860A500,
  59012161810470474620
)
...
0x6697fA48f7335F4D59655aA4910F517ec4109987::swapToAddress(USDC, 1, 43114, 0xB97E..., 0xB97E...)
...
BEP20TokenImplementation::transferFrom(
  0x4826e896E39DC96A8504588D21e9D44750435e2D,
  0x6697fA48f7335F4D59655aA4910F517ec4109987,
  1
)

The same trace also shows the prerequisite dust seeding step immediately before the exploit call:

0xd99c7F6C65857AC913a8f880A4cb84032AB2FC5b::swap(2271374, 0, 0x4826...35e2D, 0x)
...
Transfer(from: 0xd99c..., to: 0x4826...35e2D, value: 2271374)

That dust transfer is why the exploit never reverts in FundManager. FiberRouter entered swapAndCrossOneInch with a positive USDC balance, approved FundManager for 1, executed the malicious token call, and then let FundManager pull just 1 unit. The theft therefore did not depend on moving the stolen victim balance into FiberRouter; it depended only on FiberRouter becoming the effective USDC spender during the raw external call.

4.3 State Changes and Profit Predicate

The collector balance diff confirms the exact economic result:

{
  "native_balance_deltas": [
    {
      "address": "0x4d9c558660d8165f0c31566fc861978f16181ead",
      "delta_wei": "254159737186739733"
    }
  ],
  "erc20_balance_deltas": [
    {
      "token": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d",
      "holder": "0x4da35bf35504d77e5c5e9db6a35b76eb4479306a",
      "delta": "-59012161810470474620"
    },
    {
      "token": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d",
      "holder": "0x4826e896e39dc96a8504588d21e9d44750435e2d",
      "delta": "2271373"
    },
    {
      "token": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d",
      "holder": "0x6697fa48f7335f4d59655aa4910f517ec4109987",
      "delta": "1"
    }
  ]
}

Those deltas match the trace exactly:

  • victim holder balance was fully drained,
  • FundManager received only the configured amountCrossMin = 1,
  • FiberRouter retained the remaining 2271373 seeded dust units,
  • the sender EOA finished with a positive native delta of 254159737186739733 wei after gas and constructor funding.

This is therefore a deterministic ACT opportunity. Any unprivileged actor observing the public allowance and public FundManager configuration could have sent a materially equivalent transaction.

5. Adversary Flow Analysis

  1. The adversary EOA 0x4D9c558660D8165F0C31566Fc861978f16181eaD sent transaction 0x7260ad0e4769ae68f0a680356c63140353c18d7be1b86a8c4e99a0fc3b6842c1 and created helper contract 0xfbE9B2307C0b1cf9601f927332b3a5942860A500.
  2. The helper swapped 10000 wei of BNB through PancakeRouter and routed the resulting 2271374 USDC units to FiberRouter, establishing the minimum bridgeable balance.
  3. The helper called FiberRouter.swapAndCrossOneInch with swapRouter = USDC, amountIn = 0, amountCrossMin = 1, fromToken = USDC, foundryToken = USDC, and _calldata = abi.encodeWithSelector(transferFrom, victim, helper, victimBalance).
  4. FiberRouter executed the raw token call in its own context, so USDC treated FiberRouter as the spender and transferred the victim's full approved balance to the helper.
  5. FundManager then pulled exactly 1 USDC from FiberRouter for the bridge leg and emitted the normal swap event, which masked the theft inside an otherwise valid router flow.
  6. The helper approved PancakeRouter, swapped the stolen USDC for WBNB/BNB, and paid the proceeds back to the adversary EOA.

The attacker-related accounts identified in the validated analysis are:

  • EOA 0x4D9c558660D8165F0C31566Fc861978f16181eaD: exploit sender and final profit recipient.
  • Contract 0xfbE9B2307C0b1cf9601f927332b3a5942860A500: helper created in the exploit transaction, recipient of the stolen USDC, and immediate monetization contract.

The affected parties are:

  • FiberRouter 0x4826e896e39dc96a8504588d21e9d44750435e2d: vulnerable protocol entrypoint.
  • FundManager 0x6697fa48f7335f4d59655aa4910f517ec4109987: trusted downstream bridge component.
  • Allowance-bearing holder 0x4da35bf35504d77e5c5e9db6a35b76eb4479306a: direct token-loss victim.

6. Impact & Losses

The direct asset loss was the victim holder's entire BSC USDC balance:

  • Token: USDC
  • Raw loss amount: 59012161810470474620
  • Decimals: 18

The attacker monetized that theft in the same transaction:

  • Gross BNB proceeds from the final USDC sale: 258484460186739733 wei
  • Gas paid by the sender EOA: 4324713000000000 wei
  • Net sender native balance delta: 254159737186739733 wei

The scope of impact is broader than a single holder because any address with a live ERC20 allowance to FiberRouter was exposed to the same arbitrary-call abuse until the protocol fixed or disabled the vulnerable path.

7. References

  • Exploit transaction: 0x7260ad0e4769ae68f0a680356c63140353c18d7be1b86a8c4e99a0fc3b6842c1
  • Collector trace: /workspace/session/artifacts/collector/seed/56/0x7260ad0e4769ae68f0a680356c63140353c18d7be1b86a8c4e99a0fc3b6842c1/trace.cast.log
  • Collector balance diff: /workspace/session/artifacts/collector/seed/56/0x7260ad0e4769ae68f0a680356c63140353c18d7be1b86a8c4e99a0fc3b6842c1/balance_diff.json
  • Collector metadata: /workspace/session/artifacts/collector/seed/56/0x7260ad0e4769ae68f0a680356c63140353c18d7be1b86a8c4e99a0fc3b6842c1/metadata.json
  • FiberRouter verified source: https://bscscan.com/address/0x4826e896e39dc96a8504588d21e9d44750435e2d#code
  • FundManager verified source: https://bscscan.com/address/0x6697fa48f7335f4d59655aa4910f517ec4109987#code
  • BscScan transaction page: https://bscscan.com/tx/0x7260ad0e4769ae68f0a680356c63140353c18d7be1b86a8c4e99a0fc3b6842c1