Reproduced Exploit

Sablier / PRBProxy — Owner can be temporarily changed within proxy calls

1. execute DELEGATECALLs a target and only checks that owner is unchanged after return. 2. The target overwrites storage slot 0 (owner) mid-call to a colluding contract. 3. That temporary owner re-enters execute to cancel streams and sweep refunds. 4. Original owner is restored so the final check p…

Jul 2023Otheruntagged2 min read

Chain

Other

Category

untagged

Date

Jul 2023

Source

AuditVault

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.

Loading fork state…

Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 54672-owner-can-be-temporarily-changed-within-proxy-calls-allowing. Standalone Foundry PoC and full write-up: 54672-owner-can-be-temporarily-changed-within-proxy-calls-allowing_exp in the evm-hack-registry mirror.


Vulnerability classes: admin-takeover · direct-drain · known-pattern

Reproduction: self-contained Foundry PoC with only forge-std — no fork. output.txt · test/54672-…sol.

AuditVault taxonomy: lang/solidity · platform/cantina · severity/high · sector/streaming · sector/staking · genome: unbounded-loop · direct-drain · admin-takeover · known-pattern


Key info#

ImpactHIGH — mid-call owner swap steals stream cancel refunds (and any owner-gated action)
ProtocolSablier (PRBProxy) — _safeDelegateCall
Vulnerable codeEnd-of-call if (owner_ != owner) revert only
Bug classTransient storage ownership / TOCTOU on owner
FindingCantina — Sablier, Jul 2023 · #54672 · reporter Zach Obront
Reportcantina_sablier_jul2023.pdf
SourceAuditVault
FixImmutable / non-transferable owner, or registry-based ownership checks
Compiler^0.8.24 (PoC)

TL;DR#

  1. execute DELEGATECALLs a target and only checks that owner is unchanged after return.
  2. The target overwrites storage slot 0 (owner) mid-call to a colluding contract.
  3. That temporary owner re-enters execute to cancel streams and sweep refunds.
  4. Original owner is restored so the final check passes — theft succeeds silently.

Diagrams#

flowchart TD A["Alice: proxy.execute ContractA"] --> B["DELEGATECALL ContractA"] B --> C["sstore owner = ContractB"] C --> D["ContractB.execute cancel stream"] D --> E["Refund to ContractB treasury"] E --> F["sstore owner = Alice"] F --> G["post-check owner_ == owner passes"]

Impact#

Any value controlled by the proxy owner mid-execution (Sablier refunds, approvals, plugin installs) can be redirected to an attacker while the post-call owner check still succeeds.

Sources#


Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

  • Web3Sec X hacked database: search.
  • Rekt leaderboard: search.
  • Solodit incident search: search.

These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.