Lybra Finance - zaevlad'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: 50/132

Findings: 3

Award: $171.84

QA:
grade-b

🌟 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
sponsor confirmed
edited-by-warden
duplicate-769

Awards

143.4901 USDC - $143.49

External Links

Lines of code

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

Vulnerability details

Impact

Maliciuos user can use executeFlashloan() and steal all available EUSD tokens.

Proof of Concept

Here is a vulnerable function:

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); }

It allows to pass an arbitrary bytes calldata data as one of the arguments. Later it calls receiver, that can be PeUSDMainnetStableVision contract itself, with a malicious data.

... receiver.onFlashLoan(shareAmount, data); ...

So malicious user can pass next arguments:

executeFlashloan( PeUSDMainnetStableVision , 1, abi.encodeWithSignature("approve(address,uint256)", attacker, amount));

Step by step:

  1. Hacker pass malicious arguments;
  2. PeUSDMainnetStableVision calculates the share amount and transfer it to itself;
  3. Call an approve function in malicious data;
  4. Transfer loaned token back to itself again, calculate fee and burn it;

Right after that hacker will be approved and be able to transfer all EUSD tokens from the contract.

Sorry for no tests included. I was little bit confused with setting up all params.

P.S. The same issue was in Damn Vulnerably DeFi Challenges (Truster).

Tools Used

Manual review.

You can provide an additional checks to prohibit calls for approve functions or receiver should not be the same as PeUSDMainnetStableVision contract.

Assessed type

Other

#0 - c4-pre-sort

2023-07-04T14:00:02Z

JeffCX marked the issue as primary issue

#1 - c4-sponsor

2023-07-14T09:45:27Z

LybraFinance marked the issue as sponsor confirmed

#2 - c4-judge

2023-07-26T00:30:05Z

0xean marked the issue as satisfactory

#3 - c4-judge

2023-07-28T19:51:56Z

0xean marked issue #769 as primary and marked this issue as a duplicate of 769

Awards

18.4208 USDC - $18.42

Labels

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

External Links

Lines of code

https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/configuration/LybraConfigurator.sol#L85 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/configuration/LybraConfigurator.sol#L90 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/governance/GovernanceTimelock.sol#L25 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/governance/GovernanceTimelock.sol#L29

Vulnerability details

Impact

Core functions are unprotected due to a missing require check in the modifier, so malicious user can call them and break the protocol.

Proof of Concept

LybraConfigurator contract has two modifiers that should check the specific role of the user to have an ability to call some core functions. For example:

modifier onlyRole(bytes32 role) { GovernanceTimelock.checkOnlyRole(role, msg.sender); _; }

However checkOnlyRole() only returns a bool whether a user has or has not a role.

function checkOnlyRole(bytes32 role, address _sender) public view returns(bool){ return hasRole(role, _sender); } AccessControl.sol function hasRole(bytes32 role, address account) public view virtual override returns (bool) { return _roles[role].members[account]; }

So all functions that should be protected by these modifiers stay unprotected to anyone.

Tools Used

Manual review.

Developers should add a require check to the modifiers, like:

modifier onlyRole(bytes32 role) { require(GovernanceTimelock.checkOnlyRole(role, msg.sender), "Not a role"); _; }

Assessed type

Access Control

#0 - c4-pre-sort

2023-07-08T15:20:15Z

JeffCX marked the issue as duplicate of #704

#1 - c4-judge

2023-07-28T15:24:22Z

0xean changed the severity to 3 (High Risk)

#2 - c4-judge

2023-07-28T17:18:56Z

0xean marked the issue as satisfactory

Awards

9.931 USDC - $9.93

Labels

bug
downgraded by judge
grade-b
QA (Quality Assurance)
edited-by-warden
duplicate-958
Q-38

External Links

Lines of code

https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L67 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L181 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/interfaces/IPeUSD.sol#L11 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/token/PeUSD.sol#L8 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L200

Vulnerability details

Impact

Users will not be able to mint or burn PeUSD tokens and use core functions of the protocol.

Proof of Concept

When a user want to deposit his assets via LybraPeUSDVaultBase contract he calls depositAssetToMint function that has internal call to _mintPeUSD().

MintPeUSD should forward a call to a mint function of PeUSD contract:

... PeUSD.mint(_onBehalfOf, _mintAmount); ...

However that specific mint() function can be found only in interface contract, but not in the main PeUSD contract. So any call will revert.

The same situation with a burn function when user wants to get his colateral back via repay(). Here is a call to the absence function:

... PeUSD.burn(_provider, amount - totalFee); ...

Tools Used

Manual review

Provide a mint() and burn() functions with required logic for that token. Base on the developers needs it can be similar to EUSD token.

Assessed type

ERC20

#0 - c4-pre-sort

2023-07-08T19:12:40Z

JeffCX marked the issue as duplicate of #958

#1 - c4-judge

2023-07-28T18:21:13Z

0xean changed the severity to QA (Quality Assurance)

#2 - c4-judge

2023-07-28T18:21:48Z

0xean marked the issue as grade-b

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