All incidents

BSC WBNB allowance drain from unsafe spender approvals

Share
Sep 18, 2024 09:26 UTCAttackLoss: 0.18 BNBManually checked1 exploit txWindow: Atomic

Root Cause Analysis

BSC WBNB allowance drain from unsafe spender approvals

1. Incident Overview TL;DR

On BSC (chainid 56) at block 42357808, an unprivileged EOA 0xba35d089addac99a8e7bcd1a25712b1702623ae3 sent a single transaction to router contract 0xd310431e98412eb9a7c66808478bf08fdea81e2a. The router forwarded a call into helper contract 0x71cd31a564ff30ba61d7167a02babc1484034e84, which used an existing WBNB allowance from victim address 0x766a0936ff0ad045d39871846194edbd5df63a58 to execute WBNB.transferFrom into the router. The router then called WBNB.withdraw on the canonical WBNB contract 0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c, unwrapping the captured WBNB to native BNB and sending the resulting BNB back to the attacker EOA in the same transaction. Native-balance diffs show that the attacker’s BNB holdings increased by 184093635600000000 wei while the WBNB contract’s native reserves decreased by 184162062600000000 wei, with the remainder covering gas and the attacker’s small input value.

2. Key Background

The incident involves the standard wrapped BNB (WBNB) contract on BSC at 0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c, which holds native BNB and exposes ERC‑20 style approve/transferFrom as well as a withdraw(uint256) function to unwrap WBNB into BNB. The decompiled WBNB code shows that withdraw debits the caller’s internal WBNB balance and transfers an equal amount of native BNB from the contract to the caller:

// WBNB 0xbb4c...5095c — withdraw logic (decompiled)
function withdraw(uint256 arg0) public {
    address var_a = address(msg.sender);
    var_b = 0x03;
    require(!storage_map_a[var_a] < arg0);
    var_a = address(msg.sender);
    var_b = 0x03;
    storage_map_a[var_a] = storage_map_a[var_a] - arg0;
    (bool success, bytes memory ret0) = address(msg.sender).transfer(arg0);
    uint256 var_c = arg0;
    emit Withdrawal(address(msg.sender), arg0);
}

Router contract 0xd310431e98412eb9a7c66808478bf08fdea81e2a and helper contract 0x71cd31a564ff30ba61d7167a02babc1484034e84 are publicly callable contracts that the attacker can target without any privileged roles. In the observed call trace, the router uses selector 0x0044010c as its entrypoint from the attacker EOA, and then invokes the helper with selector 0x23a69e75. The helper, in turn, queries the router for a token address and calls transferFrom on WBNB, spending tokens from a victim WBNB holder who previously granted an allowance to the helper (or an equivalent spender path).

// Seed transaction trace for 0x73d459ad3c926f5247a2018197d13b2a0acbc1fc46e1e54525c210a46130a56b (excerpt)
{
  "calls": [
    {
      "from": "0xd310431e98412eb9a7c66808478bf08fdea81e2a",
      "input": "0x23a69e75…",
      "to": "0x71cd31a564ff30ba61d7167a02babc1484034e84",
      "type": "CALL",
      "value": "0x0",
      "calls": [
        {
          "from": "0x71cd31a564ff30ba61d7167a02babc1484034e84",
          "input": "0xd21220a7",
          "to": "0xd310431e98412eb9a7c66808478bf08fdea81e2a",
          "type": "STATICCALL"
        },
        {
          "from": "0x71cd31a564ff30ba61d7167a02babc1484034e84",
          "input": "0x23b872dd…00000000766a0936ff0ad045d39871846194edbd5df63a58…00000000d310431e98412eb9a7c66808478bf08fdea81e2a…000000000000000000000000000000000000000000000000028e466b92605200",
          "to": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c",
          "type": "CALL"
        }
      ]
    }
  ]
}

Victim address 0x766a0936ff0ad045d39871846194edbd5df63a58 holds WBNB and has granted a WBNB allowance that permits the helper to call WBNB.transferFrom on its balance. The on-chain APIs used in data collection could not retrieve full historical transaction lists for the attacker, router, helper, or victim addresses (due to Etherscan HTTP 403 and QuickNode JSON‑RPC method errors), so we cannot see when the approval and deposits were made, but the successful transferFrom and withdraw calls in the trace are sufficient to establish that the necessary pre‑state existed.

3. Vulnerability Analysis & Root Cause Summary

The root cause is a publicly callable helper/router path that allows any unprivileged EOA to leverage an existing WBNB allowance to convert a victim’s WBNB into native BNB and send the proceeds to the attacker within a single transaction. WBNB itself behaves according to its expected design: it tracks internal balances, honors approve/transferFrom, and unpacks value via withdraw to msg.sender. The exploit arises from how the helper and router compose these primitives: the helper spends WBNB from the victim into the router, and the router immediately withdraws that WBNB and forwards all resulting BNB to the attacker EOA. From the publicly reconstructible pre‑state immediately before block 42357808, this behavior constitutes an anyone-can-take (ACT) opportunity: any EOA capable of crafting the same calldata can replicate the attack without needing privileged roles, validator control, or off-chain secrets. The vulnerability class is improper use of token allowances through an unguarded helper/router flow that routes value to arbitrary third parties.

4. Detailed Root Cause Analysis

The explicit security invariant relevant to this incident is:

For any WBNB holder address H and any unprivileged EOA A, there should be no transaction crafted solely by A that (i) spends H’s WBNB via WBNB.transferFrom and WBNB.withdraw and (ii) delivers the resulting BNB directly to A, without any signature from H in that transaction.

This invariant is violated in the seed transaction 0x73d459ad3c926f5247a2018197d13b2a0acbc1fc46e1e54525c210a46130a56b on BSC. The reconstructed call sequence from tx_trace_callTracer.json is:

  • The attacker EOA 0xba35d089addac99a8e7bcd1a25712b1702623ae3 calls router 0xd310431e98412eb9a7c66808478bf08fdea81e2a with selector 0x0044010c and small input value (0xdc0 wei).
  • The router issues a CALL to helper 0x71cd31a564ff30ba61d7167a02babc1484034e84 with selector 0x23a69e75, passing among its arguments the router address, victim address 0x766a0936ff0ad045d39871846194edbd5df63a58, and an amount 0x028e466b92605200.
  • Inside that helper call:
    • The helper performs a STATICCALL to the router (selector 0xd21220a7), receiving 0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c as the token address.
    • The helper then calls WBNB at 0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c with selector 0x23b872dd (transferFrom), moving 0x028e466b92605200 units of WBNB from 0x766a0936ff0ad045d39871846194edbd5df63a58 to the router 0xd310431e98412eb9a7c66808478bf08fdea81e2a. This call succeeds, which implies that the pre‑state includes a sufficient allowance from the victim to the helper (or equivalent spender path).
// transferFrom and withdraw path (excerpt)
{
  "from": "0x71cd31a564ff30ba61d7167a02babc1484034e84",
  "to": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c",
  "input": "0x23b872dd000000000000000000000000766a0936ff0ad045d39871846194edbd5df63a58000000000000000000000000d310431e98412eb9a7c66808478bf08fdea81e2a000000000000000000000000000000000000000000000000028e466b92605200",
  "type": "CALL",
  "value": "0x0"
},
{
  "from": "0xd310431e98412eb9a7c66808478bf08fdea81e2a",
  "to": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c",
  "input": "0x70a08231…d310431e98412eb9a7c66808478bf08fdea81e2a",
  "type": "STATICCALL"
},
{
  "from": "0xd310431e98412eb9a7c66808478bf08fdea81e2a",
  "to": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c",
  "input": "0x2e1a7d4d000000000000000000000000000000000000000000000000028e466b92605200",
  "type": "CALL"
}
  • After WBNB has been transferred into the router, the router invokes its own function with selector 0x7ddd1537, which:
    • Calls WBNB.balanceOf(router) via 0x70a08231, observing a balance of 0x028e466b92605201.
    • Calls WBNB.withdraw(0x028e466b92605200) via selector 0x2e1a7d4d, causing the WBNB contract to send 0x028e466b92605200 wei of native BNB (decimal 184162062600000000) from its reserves to the router.
  • Finally, the router sends 0x028e466b92605fc0 wei (184162062600003520 wei) to the attacker EOA 0xba35d089addac99a8e7bcd1a25712b1702623ae3.

Because only the attacker EOA signs this transaction, and the victim 0x766a0936ff0ad045d39871846194edbd5df63a58 does not participate in the transaction’s authorization, the helper/router path lets an arbitrary EOA convert the victim’s WBNB allowance into BNB for its own benefit. WBNB’s own logic is consistent with a standard wrapped-asset implementation; the exploitable condition is that the helper and router combine transferFrom and withdraw in a way that routes the unwrapped BNB to an attacker-controlled EOA rather than to the victim or a tightly scoped beneficiary.

The primary vulnerable components are:

  • Helper contract 0x71cd31a564ff30ba61d7167a02babc1484034e84, whose selector 0x23a69e75 flow, as observed in the trace, reads the token address from the router and executes transferFrom on WBNB from the victim to the router.
  • Router contract 0xd310431e98412eb9a7c66808478bf08fdea81e2a, whose 0x7ddd1537 function withdraws WBNB held by the router and forwards the resulting BNB to the attacker EOA.
  • The pre-existing WBNB allowance from victim 0x766a0936ff0ad045d39871846194edbd5df63a58 to the helper (or equivalent spender), which enables the transferFrom to succeed without any per‑call confirmation by the victim.

The ACT exploit conditions distilled from the artifacts are:

  • Victim 0x766a0936ff0ad045d39871846194edbd5df63a58 holds at least 0x028e466b92605200 WBNB on 0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c.
  • The victim has granted a WBNB allowance to the helper 0x71cd31a564ff30ba61d7167a02babc1484034e84 (or to an equivalent spender used in the helper/router path) sufficient for the transferFrom to succeed.
  • The router and helper contracts are deployed with the runtime code reflected in the decompilations and remain publicly callable.
  • An unprivileged EOA can submit a transaction equivalent to the seed tx, targeting the router with the same calldata and gas, to realize the same sequence of calls and state transitions.

5. Adversary Flow Analysis

The adversary-related cluster and victim candidate identified from the artifacts are:

  • Adversary EOA: 0xba35d089addac99a8e7bcd1a25712b1702623ae3 (sender and recipient of profit in the seed transaction).
  • Router contract: 0xd310431e98412eb9a7c66808478bf08fdea81e2a (direct callee of the attacker and orchestrator of helper and WBNB interactions).
  • Helper contract: 0x71cd31a564ff30ba61d7167a02babc1484034e84 (performs transferFrom from the victim to the router).
  • Victim WBNB holder (candidate): 0x766a0936ff0ad045d39871846194edbd5df63a58.

The adversary executes a single lifecycle stage:

  • Stage: Adversary single‑tx execution
    • Chain: BSC (56)
    • Transaction: 0x73d459ad3c926f5247a2018197d13b2a0acbc1fc46e1e54525c210a46130a56b
    • Block: 42357808
    • Mechanism: Public helper/router path abusing WBNB allowance

End‑to‑end flow:

  1. The attacker EOA funds itself and constructs calldata for router function 0x0044010c, embedding the helper address, helper selector, router address, victim address, amount, and a follow‑up router function (0x7ddd1537) to execute after the helper call.
  2. The router receives the call, decodes the parameters, and calls helper 0x71cd31a564ff30ba61d7167a02babc1484034e84 with selector 0x23a69e75.
  3. The helper reads the WBNB token address from the router via a STATICCALL, then calls WBNB’s transferFrom from the victim to the router for 0x028e466b92605200 units.
  4. Control returns to the router, which calls its 0x7ddd1537 function, reading its WBNB balance via balanceOf and then calling withdraw on WBNB for the same amount, receiving an equal amount of BNB from the WBNB contract.
  5. The router immediately forwards almost all of this BNB (184162062600003520 wei) to the attacker EOA, retaining only enough to cover gas and its own call‑level needs.
  6. Gas accounting based on tx_metadata.json, tx_receipt.json, and balance_diff.json shows that after subtracting gas fees at 1 gwei and the attacker’s tiny input, the attacker ends the transaction with a net gain of 184093635600000000 wei of BNB.

This strategy is an ACT opportunity because any unprivileged EOA with knowledge of the helper/router interfaces and the victim’s outstanding allowance can replicate the same calldata and transaction structure to drain the victim’s WBNB into BNB for its own benefit, as long as the pre‑state conditions hold.

6. Impact & Losses

The on‑chain impact for the observed exploit transaction is:

  • WBNB reserves at 0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c lose 184162062600000000 wei of native BNB, as recorded in balance_diff.json.
  • The economic value of victim 0x766a0936ff0ad045d39871846194edbd5df63a58’s WBNB holdings decreases by an amount equal to the WBNB converted and withdrawn to BNB, although the exact ERC‑20 balance delta for the victim is not included in the provided diff.
  • Attacker EOA 0xba35d089addac99a8e7bcd1a25712b1702623ae3 realizes a net profit of 184093635600000000 wei of BNB after fees.
  • No broader protocol‑level invariant break in WBNB itself or systemic price impact is evident from the available artifacts; the harm is concentrated in the misdirected use of the victim’s allowance and the associated reserve outflow.

In tokenized form, the primary quantified loss is:

  • Token: BNB
  • Amount: -184162062600000000 wei (lost from WBNB reserves, corresponding one‑for‑one to BNB unwrapped and sent out)

7. References

  • Seed transaction: BSC tx 0x73d459ad3c926f5247a2018197d13b2a0acbc1fc46e1e54525c210a46130a56b (metadata, receipt, and geth callTracer trace).
  • WBNB contract: decompiled code for 0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c, including withdraw(uint256) and ERC‑20 functions.
  • Router contract: decompiled code for 0xd310431e98412eb9a7c66808478bf08fdea81e2a, including the 0x7ddd1537 flow that withdraws WBNB and forwards BNB.
  • Helper contract: decompiled code for 0x71cd31a564ff30ba61d7167a02babc1484034e84 and runtime ABI information showing selector 0x23a69e75 is used in the exploit path.
  • Balance diffs: balance_diff.json for the seed transaction, detailing native BNB balance changes for the attacker and WBNB contract.