code-423n4 / 2023-06-lybra-findings

8 stars 7 forks source link

attacker can get most of `EUSD` in lybra protocol by make a malicious call to the `executeFlashloan` function #569

Closed code423n4 closed 1 year ago

code423n4 commented 1 year ago

Lines of code

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/token/PeUSDMainnetStableVision.sol#L129-L139 https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/token/PeUSDMainnetStableVision.sol#L79-L88 https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/token/PeUSDMainnetStableVision.sol#L114-L121

Vulnerability details

Impact

the function executeFlashloan allow users to make a quick borrowing of EUSD stablecoin and return the flash loaned amount in the same block and in the same transaction, because of the flash loan allow you to borrow huge amount of eusd an attacker can call executeFlashloan by a contract(attacker created this contract) to get a large amount of eusd and calling convertToPeUSD directly in the (attacker contract) to update his eusd balance in the PeUSDMainnetStableVision contract. more details in POC.

Proof of Concept

the function executeFlashloan used to get flash loan amount of eusd and repay it in the same transaction and in same block(execute and repay in 12.09 seconds in Ethereum blockchain) and the convertToPeUSD function are used to deposit EUSD and mint PeUSD, an attacker can make flashloan attack by following the scenario below: the attack flow:

executeFlashloan --> contract A(receive eusd amount) --> call to onFlashloan(here the attacker can make call to the convertToPeUSD before or after or in the same time the onFlashloan execute)

1-an attacker create a contract called A and this contract contain the functionality to call the executeFlashloan and in the same time/block calling the convertToPeUSD function to deposit the EUSD that he got from the executeFlashloan function:


function executeFlashloan(FlashBorrower receiver, uint256 eusdAmount, bytes calldata data) public payable {
        //@audit this funtion will calculate the amount we get before making call to our malicious contract.
        uint256 shareAmount = EUSD.getSharesByMintedEUSD(eusdAmount);
        EUSD.transferShares(address(receiver), shareAmount);
        receiver.onFlashLoan(shareAmount, data);
        //@audit here we used the shareAmount above to send it to this contract and repay the flash loan amount
        bool success = EUSD.transferFrom(address(receiver), address(this), EUSD.getMintedEUSDByShares(shareAmount));
        require(success, "TF");

        uint256 burnShare = getFee(shareAmount);
        EUSD.burnShares(address(receiver), burnShare);
        emit Flashloaned(receiver, eusdAmount, burnShare);
    }

2-as we mentioned in the code above, the shareAmount are calculated before a call made to our malicious contract and after we receive the eusd amount our contract will make a call to the convertToPeUSD function:

function convertToPeUSD(address user, uint256 eusdAmount) public {
        require(_msgSender() == user || _msgSender() == address(this), "MDM");

        //@audit the attacker can set amount near or equal to the max eusd locked(according to time we write this attack total locked
        // is 55 million eusd according to total staked eusd on lybra website )
        require(EUSD.balanceOf(address(this)) + eusdAmount <= configurator.getEUSDMaxLocked(),"ESL");
        bool success = EUSD.transferFrom(user, address(this), eusdAmount);
        require(success, "TF");
        uint256 share = EUSD.getSharesByMintedEUSD(eusdAmount);
        //@audit update user/attacker balances
        userConvertInfo[user].depositedEUSDShares += share;
        userConvertInfo[user].mintedPeUSD += eusdAmount;
        _mint(user, eusdAmount);
    }

3-as you can see if the attacker make a call using the flashloan amount he borrowed then he can get 50 million PeUSD and updating his depositedEUSDShares and his mintedPeUSD and then repay back the flash loan in this case the attacker can hold peusd or convert it to eusd after the flashloan end by calling the convertToEUSD function.

please not that the attacker can even call the convertToEUSD in the same transaction that happened above to get EUSD and avoid any bad case happen to his attack scenario(to avoid something like Q1 below)

some question are made here so let's answer it and explain it better:

Q1: how this attack can possible and we get the sharesAmount in flashloan and then we transfer it to this contract again ? this won't work because we should transfer the eusd to the address(this) in the convertToPeUSD right ? so how we can make this attack possible?

Answer: the shareAmount in the executeFlashloan function are calculated before a call made to our onFlashloan function in our contract and that's mean when we receive the flash loaned eusd amount we then make call to the convertToPeUSD and true we send all the eusd amount to the address(this) in this way but if you recognize that the shareAmount still contain the number of the eusd and when we reach the repaying line in the executeFlashloan function we use the shareAmount that we calculated before the call made to our contract so its like we send the same amount to this contract twice one we send eusd and another convert the eusd to share and send it again:

executeFlashloan(): bool success = EUSD.transferFrom(address(receiver), address(this), EUSD.getMintedEUSDByShares(shareAmount))

convertToPeUSD(): bool success = EUSD.transferFrom(user, address(this), eusdAmount)

Q2/ how all this can happen in one contract because flashloan should get pay back in same time ?

answer/ this is possible and we saw many attacks happen because of this mechanism and borrow and update balance and increase/decrease rate also much more attack can happen because of flashloans.

and at the end the attacker has updated his balance with 50% of the eusd in the protocol and he even can get must of vote power by doing this attack(i will submit this one in another report)

Tools Used

manual review

Recommended Mitigation Steps

add nonFlashloanmodifier that prevent any flashloan call to the convertToPeUSD and even for convertToEUSD. many famous protocols using a modifier to avoid flashloan attacks scenario and this protocol should use some mechanism similar to the famous protocol to avoid bad cases happen because of flashloans in future(increase rate/ manipulate rates in other pool and other bad cases...)

Assessed type

Other

c4-pre-sort commented 1 year ago

JeffCX marked the issue as duplicate of #269

c4-judge commented 1 year ago

0xean marked the issue as unsatisfactory: Invalid