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:
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...)
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 ofEUSD
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 callexecuteFlashloan
by a contract(attacker created this contract) to get a large amount of eusd and callingconvertToPeUSD
directly in the (attacker contract) to update his eusd balance in thePeUSDMainnetStableVision
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 theconvertToPeUSD
function are used to depositEUSD
and mintPeUSD
, 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 theconvertToPeUSD
before or after or in the same time theonFlashloan
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 theconvertToPeUSD
function to deposit the EUSD that he got from theexecuteFlashloan
function: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 theconvertToPeUSD
function: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 hismintedPeUSD
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 theconvertToEUSD
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 theaddress(this)
in theconvertToPeUSD
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 theconvertToPeUSD
and true we send all the eusd amount to theaddress(this)
in this way but if you recognize that theshareAmount
still contain the number of the eusd and when we reach the repaying line in theexecuteFlashloan
function we use theshareAmount
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
nonFlashloan
modifier that prevent any flashloan call to theconvertToPeUSD
and even forconvertToEUSD
. 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