20,000 USDC
View results
Submission Details
Severity: medium

Phishing users into repaying the attacker's loans

Summary

The current design choice of the Lender::repay() is prone to phishing attacks, tricking users into repaying the attacker's loans.

Vulnerability Details

The repay() allows anyone to repay a loan for a borrower. In other words, the function caller (msg.sender) will pay the principal (loan.debt), lender's interest (lenderInterest), and protocol's interest (protocolInterest) for a borrower instead.

This is a design choice confirmed by the client. However, this design can be prone to phishing attacks. If the protocol's website (front-end) is compromised, an attacker can trick users into repaying the attacker's loans easily by putting their loans into the function parameter loanIds.

function repay(uint256[] calldata loanIds) public {
for (uint256 i = 0; i < loanIds.length; i++) {
...
// transfer the loan tokens from the borrower to the pool
IERC20(loan.loanToken).transferFrom(
@> msg.sender, //@audit -- the caller's loan tokens will be transferred to the pool
address(this),
loan.debt + lenderInterest
);
// transfer the protocol fee to the fee receiver
IERC20(loan.loanToken).transferFrom(
@> msg.sender, //@audit -- the caller's loan tokens will be transferred to the fee receiver
feeReceiver,
protocolInterest
);
...
}
}
  • The caller's loan tokens will be transferred to the pool: https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L318

  • The caller's loan tokens will be transferred to the fee receiver: https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L324

Impact

As described in the Vulnerability Details section, the current design choice of the repay() can be an attack vector for phishing users to repay the attacker's loans easily in case the protocol's website (front-end) is compromised.

Tools Used

Manual Review

Recommendations

I recommend enforcing only a borrower to be able to repay a loan, as shown below.

function repay(uint256[] calldata loanIds) public {
for (uint256 i = 0; i < loanIds.length; i++) {
...
+ if (msg.sender != loan.borrower) revert Unauthorized();
...
// transfer the loan tokens from the borrower to the pool
IERC20(loan.loanToken).transferFrom(
msg.sender,
address(this),
loan.debt + lenderInterest
);
// transfer the protocol fee to the fee receiver
IERC20(loan.loanToken).transferFrom(
msg.sender,
feeReceiver,
protocolInterest
);
...
}
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.