code-423n4 / 2023-12-ethereumcreditguild-findings

17 stars 11 forks source link

Unverified Auction Pricing Inputs Enabling Manipulated Bidding & Valuation #341

Closed c4-bot-9 closed 9 months ago

c4-bot-9 commented 10 months ago

Lines of code

https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/AuctionHouse.sol#L118-L161 https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/LendingTerm.sol#L678-L680 https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/AuctionHouse.sol#L166-L196

Vulnerability details

Users could manipulate auction outcomes on how bid pricing is determined. This is critical for fair collateral valuation. The key mechanism is the AuctionHouse.getBidDetail() function:

function getBidDetail(
    bytes32 loanId
) public view returns (uint256 collateralReceived, uint256 creditAsked) {
    // check the auction for this loan exists
    uint256 _startTime = auctions[loanId].startTime;
    require(_startTime != 0, "AuctionHouse: invalid auction");

    // check the auction for this loan isn't ended
    require(auctions[loanId].endTime == 0, "AuctionHouse: auction ended");

    // assertion should never fail because when an auction is created,
    // block.timestamp is recorded as the auction start time, and we check in previous
    // lines that start time != 0, so the auction has started.
    assert(block.timestamp >= _startTime);

    // first phase of the auction, where more and more collateral is offered
    if (block.timestamp < _startTime + midPoint) {
        // ask for the full debt
        creditAsked = auctions[loanId].callDebt;

        // compute amount of collateral received
        uint256 elapsed = block.timestamp - _startTime; // [0, midPoint[
        uint256 _collateralAmount = auctions[loanId].collateralAmount; // SLOAD
        collateralReceived = (_collateralAmount * elapsed) / midPoint;
    }
    // second phase of the auction, where less and less CREDIT is asked
    else if (block.timestamp < _startTime + auctionDuration) {
        // receive the full collateral
        collateralReceived = auctions[loanId].collateralAmount;

        // compute amount of CREDIT to ask
        uint256 PHASE_2_DURATION = auctionDuration - midPoint;
        uint256 elapsed = block.timestamp - _startTime - midPoint; // [0, PHASE_2_DURATION[
        uint256 _callDebt = auctions[loanId].callDebt; // SLOAD
        creditAsked = _callDebt - (_callDebt * elapsed) / PHASE_2_DURATION;
    }
    // second phase fully elapsed, anyone can receive the full collateral and give 0 CREDIT
    // in practice, somebody should have taken the arb before we reach this condition.
    else {
        // receive the full collateral
        collateralReceived = auctions[loanId].collateralAmount;
        //creditAsked = 0; // implicit
    }

  // Compute collateral and CREDIT bid amounts

  // Based on configured auction parameters

  // And current block timestamp

}

It calculates bid pricing based on.

  1. Configured auction duration and midpoint

  2. Originating loan collateral deposited

  3. Loan CREDIT debt outstanding

Over the duration, it progressively increases the collateral offered while decreasing the CREDIT asked.

However, there are no validity checks that the inputs passed match the real loan state.

Attackers could manipulate values to artificially trigger more favorable pricing.

Impact

If attackers can influence pricing functions, they could unfairly extract more collateral value while paying less outstanding debt.

Proof of Concept

No formal verification that getBidDetail() pricing parameters match the actual monitored lending market state. This enables faulty oracles to trigger improper pricing and valuation.

Scenario

  1. Eve borrows 10 ETH posting 10 ETH collateral

  2. Eve defaults on loan, entering auction with 10 ETH collateral and 20 ETH debt

  3. When auction bid pricing calculated, Eve manipulates values:

    • Collateral passed as 20 ETH instead of real 10 ETH

    • Debt passed as 10 ETH instead of real 20 ETH

  4. Auction ends early with Eve able to claim 20 ETH collateral while only repaying 10 ETH

Code Flow

  1. Eve borrows 10 ETH loan with 10 ETH collateral

  2. LendingTerm.call(): Eve defaults on 20 ETH loan debt

  3. AuctionHouse.getBidDetail(): Eve passes manipulated 20 ETH collateral, 10 ETH debt values

  4. AuctionHouse.bid(): Eve claims 20 ETH by paying 10 ETH (based on bogus inputs)

By manipulating input values to the getBidDetail() pricing function, Eve unfairly extracts additional collateral for less debt repayment.

Recommended Mitigation Steps

  1. Formally verify getBidDetail() inputs vs on-chain states

  2. Introduce redundancy checking inputs against lending market instances

  3. Cease auctions if mismatches detected

Robust input validation is key to preventing manipulated outcomes.

Assessed type

Oracle

0xSorryNotSorry commented 9 months ago

insufficient evidence.

c4-pre-sort commented 9 months ago

0xSorryNotSorry marked the issue as insufficient quality report

c4-judge commented 9 months ago

Trumpero marked the issue as unsatisfactory: Invalid