Stader Labs - broccolirob's results

Decentralized ETH liquid staking protocol with 4 ETH bond for anyone to be a node operator.

General Information

Platform: Code4rena

Start Date: 02/06/2023

Pot Size: $100,000 USDC

Total HM: 15

Participants: 75

Period: 7 days

Judge: Picodes

Total Solo HM: 5

Id: 249

League: ETH

Stader Labs

Findings Distribution

Researcher Performance

Rank: 9/75

Findings: 1

Award: $2,676.76

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: broccolirob

Also found by: 0x70C9, bin2chen, dwward3n, hals

Labels

bug
3 (High Risk)
primary issue
satisfactory
selected for report
sponsor confirmed
H-01

Awards

2676.7554 USDC - $2,676.76

External Links

Lines of code

https://github.com/code-423n4/2023-06-stader/blob/7566b5a35f32ebd55d3578b8bd05c038feb7d9cc/contracts/VaultProxy.sol#L20-L36 https://github.com/code-423n4/2023-06-stader/blob/7566b5a35f32ebd55d3578b8bd05c038feb7d9cc/contracts/VaultProxy.sol#L41-L50

Vulnerability details

Impact

When the VaultFactory contract is deployed and initialized, the initialise method on the newly created VaultProxy implementation contract is never called. As such, anyone can call that method and pass in whatever values they want as arguments. One important argument is the _staderConfig address, which controls where the fallback function will direct delegatecall operations. If an attacker passes in a contract that calls selfdestruct it will be run in the context of the VaultProxy implementation contract, and will erase all code from that address. Since the clones from the VaultProxy contract merely delegate calls to the implementation address, all subsequent calls for all created vaults from that implementation, will be treated like an EOA and return true even though calls to functions on that proxy were never executed.

Proof of Concept

  • First, an attacker deploys a contract called AttackContract that calls selfdestruct in its fallback function.
contract AttackContract { function getValidatorWithdrawalVaultImplementation() public view returns(address) { return address(this); } function getNodeELRewardVaultImplementation() public view returns(address) { return address(this); } fallback(bytes calldata _input) external payable returns(bytes memory _output) { selfdestruct(address(0)); } }
  • The attacker calls the initialise method on the VaultProxy implementation contract. That address is stored in the vaultProxyImplementation variable on the VaultFactory contract. The attacker passes in the address of AttackContract as the _staderConfig argument for the initialise function.
  • The attacker then calls a non-existent function on the VaultProxy implementation contract, which triggers it's fallback function. The fallback function calls staderConfig.getNodeELRewardVaultImplementation(), and since staderConfig is set the AttackContract address, it returns the address of the AttackContract. delegatecall runs the fallback function of AttackContract in its own execution environment. selfdestruct is called in the execution environment of the VaultProxy implementation, which erases the code at that address.
  • All cloned copies of the VaultProxy implementation contract are now forwarding calls to an implementation address that has no code stored at it. These calls will be treated like calls to an EOA and return true for success.

Tools Used

Manual Analysis

Prevent the initialise function from being called on the VaultProxy implementation contract by inheriting from OpenZeppelin's Initializable contract, like the system is doing in other contracts. Call the _disableInitializers function in the constructor and protect initialise with the initializer modifier. Alternatively, the initialise function can be called from the initialize function of the VaultFactory contract when the VaultProxy contract is instantiated.

Assessed type

Access Control

#0 - c4-judge

2023-06-10T10:48:24Z

Picodes marked the issue as duplicate of #167

#1 - c4-judge

2023-06-26T15:49:39Z

Picodes marked the issue as satisfactory

#2 - c4-judge

2023-06-26T15:51:08Z

Picodes marked the issue as selected for report

#3 - Picodes

2023-07-03T12:13:53Z

Keeping High severity as this seems exploitable to lock funds with no cost as the fallback function is payable

#4 - c4-sponsor

2023-07-04T05:27:01Z

sanjay-staderlabs marked the issue as sponsor confirmed

#5 - sanjay-staderlabs

2023-07-13T04:16:39Z

This is fixed in the code

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