Buying loan could be called by anyone to buy a loan in refinance auction. On completion, the loan's lender is set to msg.sender and the debt is moved from old pool to a new pool specified by argument poolId. However, the logic does not check whether msg.sender is the lender of pool poolId which causes anyone to use funds from poolId to buy loan. After being loan's lender, the attacker could start an auction such that the auction could not be bought by anyone. After auction period, with some tweaks, the attacker could seize the loan and get the collateral
We have 2 pools: pool with id idX (loanA-colB) by lender Alice; pool with id idY (loanA-colB) by lender Bob ; and Chad borrows from pool X with loanId = Lid. Consider pool idX and pool idY has almost same configurations excepts pool balance such that pool idY has higher pool balance. Let's say Chad's loan is now on auction.
1.An attacker execute buyLoan(Lid, idY) => debt from loan Lid is moved from pool idX to pool idY and loan's lender is set to attacker. This means that attacker used pool balance from idY to buy Lid => Alice's loan token is stolen to buy loan
2.The attacker without having any lending pools now has a loan Lid, starts to auction the loan Lid. This auction could not be bought by anyone because this line https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L502 revert because of arithmetic underflow (the fact here is the pool is not existed yet). Besides, Chad could not repay the loan because of the same reason
3.The attacker waits for auction period to pass. From here, the loan Lid is able to be seized. To bypass underflow on the line https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L575-L576 , the attacker could do some tricks to steal collateral:
create a lending pool with poolBalance > Lid.debt(because of protocol fees) and maxLoanRatio = poolBalance * 10**18
do self-borrow to with collateral == 1 and debt == Lid.debt to increase pool's outstanding loan such that outstandingLoans >= Lid.debt
seize loan and get collateral
Beside, attacker vector could also be this way:
Attacker creates his lending pool
Attacker borrows from his own pool
Start auction his loan
Buy loan using other lender's pool like in step 1 above
continue the attack as above
POC
Lenders loan token stolen
Borrowers collateral token stolen
Borrowers can not repay
Unable to buy loans
Foundry
Consider add one more line to function buyLoan(uint256,bytes32) such that: require(msg.sender == pools[poolId].lender, "Not pool lender")
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.