The buyLoan()
method in the Lender
contract allows anyone to buy the loan from an auction, as long as they have a sufficient pool. The method takes two parameters - loanId
, which is an ID of the loan to buy, and the poolId
, which is the ID of the pool to which the loan will be transferred. The problem is that the method never checks if the caller is an owner of the pool, which means that anyone can buy loans from the auction on behalf of someone else without permission. Furthermore, the method never checks if the loan terms (specifically the debt/collateral ratio) is compatible with the pool's settings (specifically the maxLoanRatio
). Those two issues combined open a way for the attacker to steal funds from any pool in the protocol.
Please consider the following scenario:
The attacker notices that there is a WETH/USDC pool in the protocol with 1 million WETH deposited as a loan token by some whale. The pool's terms specify that for each 1 WETH borrowed the borrower has to put 4000 USDC as a collateral.
The attacker creates their own WETH/USDC pool with 1,000 WETH, specifying a very high loanRatio
, effectively allowing the borrowing from their pool without barely any collateral at all.
The attacker borrows all 1,000 WETH from their pool for just 1 USDC of collateral.
The attacker puts their loan at an auction. Given the malicious terms of the loan, nobody would be willing to buy it.
The attacker waits until the auction's rate matches the whale's pool interestRate
and calls the buyLoan()
method, passing the whale's poolID
.
The whale has just bougth a loan of 1,000 WETH for just 1 USDC of a collateral (even though he's not aware of it and the loan violates his pool's terms).
The attacker repeats 2-6 until the pool is fully drained, stealing the whale's deposit (minus protocol fees) for minimal collateral.
The attacker can now repeat 2-7 for every pool in the protocol, effectively stealing all of the deposits.
The code below demonstrates the described attack vector. Save it as a .t.sol
file under test/
folder (for example, /test/ExploitStealPool.t.sol
), and run it with the following command: forge test --match-test testAttackerStealsPool
.
An attacker can steal the deposits from every pool in the protocol (minus protocol fees).
Manual review
In the buyLoan()
method, validate if the msg.sender
is the pool's lender
. Consider also additional checks for the compatibility between the loan and pool settings (for example, revert if the loan ratio is bigger than the pool maxLoanRatio
).
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.