Beginner FriendlyFoundryDeFiOracle
100 EXP
View results
Submission Details
Severity: high
Valid

Any user could take a flash loan and use `deposit` function to repay the loan, bypassing the repay mechanism and gaining free LP AssetTokens

Summary

As currently designed, the protocol allows any user to take a flashloan of an allowed token by calling the flashloan public function. The amount available to borrow for each call to flashloan function is IERC20(token).balanceOf(address(assetToken))(as AssetToken contract holds the underlying tokens). The amount to borrow will be sent to a contract that implements executeOperation(address,uint256,uint256,address,bytes) function. This function is then allowed to do whatever it wants, and must only send back borrowed tokens + fee (0.3% of the total borrowed amount) to address(assetToken) in order to satisfy the following condition :

uint256 endingBalance = token.balanceOf(address(assetToken));
if (endingBalance < startingBalance + fee) {
revert ThunderLoan__NotPaidBack(startingBalance + fee, endingBalance);
}

This can be achieved by calling repay function, which can only be called during a flashloan transaction, and executes the safeTransferFrom function. An approval must be done before calling repay function.

Nevertheless, it is also possible to call deposit function during the flashloan, achieving reimbursement but minting LP tokens in the process.

Vulnerability Details

Let's consider the following scenario :

1000 tokens have been deposited into ThunderLoan contract after the owner of the contract allowed this token in the protocol. For now, no flashloan has been executed for this token.
You deploy a contract to attack the protocol. This contract will call the flashloan function, requesting the maximum available amount of token to borrow, which is 1000. flashloan function will then callback executeOperation function on your attacker contract, which will call deposit function, sending 1003 tokens through it. This means your contract needs to hold at least 0.3% of the total amount of token you borrow through flashloan for this attack to succeed. deposit function will set mintAmount to 1003 tokens (or 1003 * 1e18 with decimal precision), as STARTING_EXCHANGE_RATE = 1e18. Ultimately, assetToken.mint(msg.sender, mintAmount) will mint you 1003 LP tokens, and you will send back your tokens with token.safeTransferFrom(msg.sender, address(assetToken), amount); (exactly the same line as in repay function). This way, flashloan initial call will succeed.

The result of this attack is :

  • 1000 tokens held by AssetToken contract

  • 2003 LP assetToken existing, and you own 1003 of them

Attacker can then call redeem function to get back the underlying tokens at the current exchange rate.

This attack can be repeated indefinitely, even if flashloans already occurred. Anyone can proceed to this attack, and the cost of it is very low (0.3% of the borrowed amount).

Impact

The severity of this finding is critical as any attack of that kind would result in lost funds for the protocol, and ultimately for users who deposited in the protocol. Each attack will dilute all LP holders.

Tools Used

Manual and Foundry

Recommendations

I suggest to make sure deposit function of ThunderLoan cannot be called during a flash loan. This way, no one could use borrowed tokens to mint LP tokens representing.

Adding :

if (s_currentlyFlashLoaning[token]) {
revert ThunderLoan__depositNotAllowedDuringFlashLoan();
}

after declaring the custom error error ThunderLoan__depositNotAllowedDuringFlashLoan(); would prevent this attack from happening.

Updates

Lead Judging Commences

0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

flash loan funds stolen by a deposit

Support

FAQs

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