Lybra Finance - Arz's results

A protocol building the first interest-bearing omnichain stablecoin backed by LSD.

General Information

Platform: Code4rena

Start Date: 23/06/2023

Pot Size: $60,500 USDC

Total HM: 31

Participants: 132

Period: 10 days

Judge: 0xean

Total Solo HM: 10

Id: 254

League: ETH

Lybra Finance

Findings Distribution

Researcher Performance

Rank: 54/132

Findings: 2

Award: $144.81

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: Neon2835

Also found by: 0xRobocop, 0xcm, Arz, DedOhWale, HE1M, MohammedRizwan, azhar, kankodu, zaevlad

Labels

bug
3 (High Risk)
satisfactory
upgraded by judge
duplicate-769

Awards

143.4901 USDC - $143.49

External Links

Lines of code

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

Vulnerability details

The executeFlashloan() in PeUSDMainnetStableVision.sol allows users to execute flash loans but the problem is that the receiver doesnt have to be the msg.sender so an attacker can do 2 things:

  1. Execute other users flash loans

  2. If a user is a smart contract that has a fallback function(for example a multisig wallet), then the attacker can call executeFlashloan() with the user contract address as the receiver and receiver.onFlashLoan() is not going to revert because the contract has a fallback function which is a special function that is executed when a function that does not exist is called

Because there is a fee for executing a flash loan, the attacker will burn shares of other users(either the flash loan contract or any contract that has a fallback function)

Impact

The attacker can burn shares of other users(either the flash loan contract or any contract that has a fallback function) by continously calling executeFlashloan() or he can just make the shareAmount so big that the fee will be huge and it will burn all the users shares.

Proof of Concept

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


function executeFlashloan(FlashBorrower receiver, uint256 eusdAmount, bytes calldata data) public payable {
     uint256 shareAmount = EUSD.getSharesByMintedEUSD(eusdAmount);
     EUSD.transferShares(address(receiver), shareAmount);
     receiver.onFlashLoan(shareAmount, data);
     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);
}

As you can see above, there is no check if msg.sender == receiver so an attacker abuse this

Example 1

  1. A multisig wallet that is a smart contract that has a fallback function(that does nothing) deposits to the Lybra protocol.
  2. The attacker sees this and then calls executeFlashloan() where receiver is the multisig wallet.
  3. Because the multisig wallet has a fallback function, receiver.onFlashLoan() call is not going to revert
  4. Because of the fee, shares from the multisig wallet will be burned and the attacker can make this fee so big that the whole balance is burned

Example 2

  1. A user has created a flash loan contract
  2. The attacker knows about this flash loan and decides to executeFlashloan() where receiver is the flash loan contract
  3. If nothing reverts in the flash loans onFlashLoan() function then the flash loan is charged a fee

Tools Used

Manual review

Use msg.sender instead of receiver so that flash loans that want to execute this function execute it themselves.

Or the second solution might be burning shares from msg.sender instead of the receiver so that flash loan contracts dont have to hold any shares.

function executeFlashloan(uint256 eusdAmount, bytes calldata data) public payable {
     uint256 shareAmount = EUSD.getSharesByMintedEUSD(eusdAmount);
     EUSD.transferShares(msg.sender, shareAmount);
     FlashBorrower(msg.sender).onFlashLoan(shareAmount, data);
     bool success = EUSD.transferFrom(msg.sender, address(this), EUSD.getMintedEUSDByShares(shareAmount));
     require(success, "TF");

     uint256 burnShare = getFee(shareAmount);
     EUSD.burnShares(msg.sender, burnShare);
     emit Flashloaned(FlashBorrower(msg.sender), eusdAmount, burnShare);
}

Assessed type

Other

#0 - c4-pre-sort

2023-07-04T14:01:05Z

JeffCX marked the issue as duplicate of #280

#1 - c4-judge

2023-07-28T15:29:33Z

0xean marked the issue as satisfactory

#2 - c4-judge

2023-07-28T19:53:20Z

0xean changed the severity to 3 (High Risk)

Awards

1.3247 USDC - $1.32

Labels

2 (Med Risk)
satisfactory
duplicate-27

External Links

Judge has assessed an item in Issue #899 as 2 risk. The relevant finding follows:

issue #!

#0 - 0xean

2023-07-27T23:53:08Z

Issue #1

#1 - c4-judge

2023-07-28T00:23:33Z

0xean marked the issue as duplicate of #27

#2 - c4-judge

2023-07-28T17:15:05Z

0xean marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter