Skip to content

Latest commit

 

History

History
149 lines (130 loc) · 7.28 KB

016.md

File metadata and controls

149 lines (130 loc) · 7.28 KB

Old Obsidian Nuthatch

High

Borrower can bypass the condition checks during matching using reentrancy.

Summary

Borrower can bypass the condition checks during matching using reentrancy.

Root Cause

  • The DebitaV3Aggregator.matchOffersV3() function checks the borrow offer's conditions (e.g., duration) against those of lend offers before calling DBOImplementation.acceptBorrowOffer() and finally creating a loan with the borrow offer's condition.
    function matchOffersV3(
        address[] memory lendOrders,
        uint[] memory lendAmountPerOrder,
        uint[] memory porcentageOfRatioPerLendOrder,
        address borrowOrder,
        address[] memory principles,
        uint[] memory indexForPrinciple_BorrowOrder,
        uint[] memory indexForCollateral_LendOrder,
        uint[] memory indexPrinciple_LendOrder
    ) external nonReentrant returns (address) {
        ...... SKIP ......
            // check that the duration is between the min and max duration from the lend order
            require(
@>              borrowInfo.duration >= lendInfo.minDuration &&
                    borrowInfo.duration <= lendInfo.maxDuration,
                "Invalid duration"
            );
        ...... SKIP ......
@>      DBOImplementation(borrowOrder).acceptBorrowOffer(
            borrowInfo.isNFT ? 1 : amountOfCollateral
        );
        ...... SKIP ......
        DebitaV3Loan deployedLoan = DebitaV3Loan(address(_loanProxy));
        // init loan
        deployedLoan.initialize(
            borrowInfo.collateral,
            principles,
            borrowInfo.isNFT,
            borrowInfo.receiptID,
            borrowInfo.isNFT ? 1 : amountOfCollateral,
            borrowInfo.valuableAssetAmount,
            amountOfCollateral,
            borrowInfo.valuableAsset,
@>          borrowInfo.duration,
            amountPerPrinciple,
            borrowID, //borrowInfo.id,
            offers,
            s_OwnershipContract,
            feeInterestLender,
            feeAddress
        );
        ...... SKIP ......
    }
    function acceptBorrowOffer(
        uint amount
    ) public onlyAggregator nonReentrant onlyAfterTimeOut {
        ...... SKIP ......
        uint percentageOfAvailableCollateral = (borrowInformation
            .availableAmount * 10000) / m_borrowInformation.startAmount;

        // if available amount is less than 0.1% of the start amount, the order is no longer active and will count as completed.
        if (percentageOfAvailableCollateral <= 10) {
            isActive = false;
            // transfer remaining collateral back to owner
            if (borrowInformation.availableAmount != 0) {
@>              SafeERC20.safeTransfer(
                    IERC20(m_borrowInformation.collateral),
                    m_borrowInformation.owner,
                    borrowInformation.availableAmount
                );
            }
            borrowInformation.availableAmount = 0;
            IDBOFactory(factoryContract).emitDelete(address(this));
            IDBOFactory(factoryContract).deleteBorrowOrder(address(this));
        } else {
            IDBOFactory(factoryContract).emitUpdate(address(this));
        }
    }
  • As per readme, "any ERC20 that follows exactly the standard" can be used as collateral for borrow offers. However, if an ERC-777 token is used as collateral, the borrower's callback function will be triggered when the collaterals are transferred. At this point, the callback function can call the DBOImplementation.updateBorrowOrder() function.
  • The DBOImplementation.updateBorrowOrder() function does not have a nonReentrant modifier.
    function updateBorrowOrder(
        uint newMaxApr,
        uint newDuration,
        uint[] memory newLTVs,
        uint[] memory newRatios
    ) public onlyOwner {
        ...... SKIP ......
    }

Internal pre-conditions

  1. A borrower creates a borrow offer with an ERC-777 token as collateral.

External pre-conditions

No response

Attack Path

The updateBorrowOrder() function can modify several parameters of the borrow offer, including APR, LTV, and duration. Here’s a simplified scenario focusing on the duration:

  1. The borrower creates a borrow offer using an ERC-777 token as collateral, with a duration of 10 days.
  2. The borrower matches their own borrow offer with lend offers by calling DebitaV3Aggregator.matchOffersV3(), where the lend offers have a duration range of 5 to 20 days.
  3. The matchOffersV3() function verifies that the borrow offer's duration falls within the lend offers' duration range.
  4. The matchOffersV3() function calls DBOImplementation.acceptBorrowOffer().
  5. The DBOImplementation.acceptBorrowOffer() function transfers the collateral to the borrower.
  6. Since the collateral is an ERC-777 token, the borrower’s callback function is invoked.
  7. The borrower’s callback function calls DBOImplementation.updateBorrowOrder(), changing the duration to 30 days.
  8. As a result, the borrower is able to borrow funds for 30 days, exceeding the maximum duration of 20 days set by the lend offers.

Impact

The borrower can manipulate key parameters, such as duration, APR, LTV, and ratios, to favorable borrowing terms that exceed the conditions agreed upon in the lending offers.

PoC

The updateBorrowOrder() function can modify several parameters of the borrow offer, including APR, LTV, and duration. Here’s a simplified scenario focusing on the duration:

  1. The borrower creates a borrow offer using an ERC-777 token as collateral, with a duration of 10 days.
  2. The borrower matches their own borrow offer with lend offers by calling DebitaV3Aggregator.matchOffersV3(), where the lend offers have a duration range of 5 to 20 days.
  3. The matchOffersV3() function verifies that the borrow offer's duration falls within the lend offers' duration range.
  4. The matchOffersV3() function calls DBOImplementation.acceptBorrowOffer().
  5. The DBOImplementation.acceptBorrowOffer() function transfers the collateral to the borrower.
  6. Since the collateral is an ERC-777 token, the borrower’s callback function is invoked.
  7. The borrower’s callback function calls DBOImplementation.updateBorrowOrder(), changing the duration to 30 days.
  8. As a result, the borrower is able to borrow funds for 30 days, exceeding the maximum duration of 20 days set by the lend offers.

Mitigation

Add the nonReentrant modifier to the DBOImplementation.updateBorrowOrder() function as follows.

    function updateBorrowOrder(
        uint newMaxApr,
        uint newDuration,
        uint[] memory newLTVs,
        uint[] memory newRatios
--  ) public onlyOwner {
++  ) public onlyOwner nonReentrant {
        ...... SKIP ......
    }