✅Meta Staking
Last updated
Last updated
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.
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 calls multicall()
, 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
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 a Transaction
struct. The calldata exploits the _msgSender()
parsing algorithm: during batchExecute()
, 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()
. Inside staking.transfer()
, _msgSender()
will be Setup contract as we explained above. Then it calls transferFrom()
and from
will be _msgSender()
, so it means “transfer 10000e18 grey token from Setup contract to Exploit contract”.