The buyLoan(uint256 loanId, bytes32 poolId)
function doesn't verify that the msg.sender
is the lender of the poolId
passed as a parameter. This enables anyone to buy loans on behalf of someone else's pools and draining them that way.
Here is how the attack would go, POC in code is below:
There is an existing pool of a normal lender - lender1
Malicious lender - lender2 creates a pool(with big maxLoanRatio
so he can get a big loan as a borrower)
From another address as a borrower he borrows a big loan from his pool
Auctions the loan, then buys it on behalf of lender1's pool, effectively draining his balance while the loanTokens lender2 transferred to the borrower are now restored(because the loan is supposed to be someone else's now)
Lender2 has the same balance as he started with, and the Borrower has his loan and collateral tokens
Lender1 can't auction, seize, or give the Borrower's loan because he is not the lender of the loan(the lender is whoever called the buyLoan
function)
This can be done for any pool in the protocol and can be done a lot more precisely with less amount of liquidity needed if the pools and their balances when deployed are known.
Add this test to Lender.t.sol:
Note: I had to run it with forge test --match-test testCleanCleanPOC -vvv --via-ir
because of a stack too deep error.
If the attack is precisely executed it can drain all the pools in the protocol in a very short time.
Manual review, Foundry
Add require(pools[poolId].lender == msg.sender)
, you have this check on almost every other function.
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.