✅Meta Staking
Codebase walkthrough
This codebase is made of 3 moving parts:
Staking.sol → This is the user entry point, user can either stake or unstake. This contract implements approve/transfer/transforFrom which looks suspicious.
Vault.sol → This is the “backend” of Staking.sol, user’s asset will be stored here when they stake and withdrawn from here when they unstake.
Relayer.sol → User can interact with this relayer and make call to arbitrary place. The relayer isn’t neccessary in this codebase but that’s how the CTF was designed. The bug is in the relayer integration.
Initially Setup.sol staked 10000 grey token into the vault. The goal is stealing all the funds in the vault.
Bug description
When you see relayer + batchExecute()
, you know this chall is mimicing ERC2771Context multicall bug. I find this bug easy to understand but hard to explain clearly, so let me explain it in a sequence diagram:
Extra explanations:
multicall()
is an inherited function in VulnerbleContract. The function is a delegatecall, so when TrustedForwarder callsmulticall()
, msg.sender will be TrustedForwarder.If caller is TrustedForwarder,
_msgSender()
returns the last 20 bytes of calldata → this leads to impersonation:
You can impersonate anyone, depending on your need and the context.
Map the terminologies from this bug to the chall:
TrustedForwarder → Relayer
VulnerableContract → Staking
elevated privilege → Setup
PoC
Attack steps:
Prepare a valid signature, it is just a normal signature that you signed, no hack here
Let
_getTransaction()
prepare a calldata and pack the calldata into aTransaction
struct. The calldata exploits the_msgSender()
parsing algorithm: duringbatchExecute()
, msg.sender will be relayer (because the call in the function is a delegatecall), so_msgSender()
returns the last 20 bytes of calldata, which is Setup contract.Let relayer execute this request. Internally relayer will call
batchExecute()
, which calls only one function:staking.transfer()
. Insidestaking.transfer()
,_msgSender()
will be Setup contract as we explained above. Then it callstransferFrom()
andfrom
will be_msgSender()
, so it means “transfer 10000e18 grey token from Setup contract to Exploit contract”.
Last updated