Munchables LockManager Proxy-Admin Malicious Upgrade Drain (2024-03-26)

  • Name: Munchables LockManager proxy-admin malicious upgrade drain
  • Date: 2024-03-26
  • Protocol: Munchables
  • Chain: Blast
  • Category: privileged upgrade abuse / malicious implementation swap / insider admin compromise
  • Loss: 17,413 ETH drained from the LockManager contract (about $62.5 million at the time); funds were later returned, so net user loss ended at zero
  • Summary: Munchables shipped its Blast lock contract behind a transparent upgradeable proxy whose admin control never safely left a rogue developer. Public investigators concluded that on 2024-03-21 the rogue admin upgraded the LockManager proxy to an unverified malicious implementation, used that temporary control window to mutate lock-accounting storage so the attacker address appeared to own an enormous ETH position, then switched the proxy back to a benign-looking implementation. Once user TVL was large enough, the attacker called the normal unlock(address,uint256) entry point on 2024-03-26 and withdrew 17,413 ETH from the pooled contract balance. The exploit path was not a novel user-facing logic bug; it was hidden root control over upgradeability.
  • Trigger / entry point:
    • The decisive entry point was retained proxy-admin authority over the production LockManager proxy.
    • Munchables used a transparent proxy wrapper (ProxyFactory) around core contracts, so whoever controlled the proxy admin could replace the live implementation at will.
    • The final drain used the ordinary unlock(address,uint256) interface with selector 0x7eee288d, after the attacker had already made protocol storage treat them as a legitimate large locker.
  • Root cause:
    • The failure was governance and deployment custody, not a subtle arithmetic bug in the public implementation.
    • A rogue insider appears to have retained or controlled upgrade authority over the live lock contract instead of transferring that authority to a trusted team-controlled multisig with verification and review.
    • Because user funds were pooled inside the lock contract and withdrawal eligibility was represented in mutable storage, proxy-admin control was effectively custody over user deposits.
    • The upgrade system had no effective trust minimization at the point that mattered: no immutable logic, no enforced delay, no requirement that upgraded implementations be verified, and no independent operational control that could have stopped a malicious implementation swap.
  • Exploit path:
    1. Munchables deployed the Blast LockManager behind a transparent proxy, making implementation code replaceable by the proxy admin.
    2. The rogue developer retained effective admin control over that proxy instead of handing it off to a safe production governance path.
    3. In upgrade transaction 0xea1d9c0d8de4280b538b6fe6dbc3636602075184651dfeb837cb03f8a19ffc4f on 2024-03-21, the proxy was switched to an unverified implementation.
    4. During that malicious-implementation window, public investigators reported that the attacker manually manipulated storage so their address would appear to have a massive ETH lock balance inside lockedTokens[msg.sender][address(0)]-style accounting.
    5. The proxy was then pointed back to a benign-looking implementation, hiding the setup phase inside what looked like an ordinary upgraded system rather than an obviously malicious live contract.
    6. On 2024-03-26, after user deposits had grown, the attacker called unlock(address,uint256) in transaction 0x9a7e4d16ed15b0367b8ad677eaf1db6a2a54663610696d69e1b4aa1a08f55c95 and withdrew 17,413 ETH from the contract.
    7. The attacker address 0x6e8836f050a315611208a5cd7e228701563d09c5 briefly began moving funds, including a small bridge-out, before public attribution pressure and intervention efforts forced a reversal.
    8. Funds were later returned in three Blast transactions: 0x69f271f90204ae993200f54676c922fe5ee3e5020a16ae34f589f52d923857f1, 0x381d57aa2d959ff9580ad61cc6549ae3c026eed9ee5b2ea10f9601a186c49a13, and 0x62a148877957cbf1ae89cafa144496d99239ee900a3b90194249e6baaa3ddc2f.
  • What the attacker needed to be true:
    • A production proxy admin had to remain under attacker control or be recoverable by the attacker without team consent.
    • The protocol had to accept upgradeability as an invisible trust boundary over live user funds.
    • User deposits had to sit in a pooled balance that a forged internal accounting entry could later draw down.
    • There could be no enforced timelock, code-verification gate, or independent review process able to stop the malicious implementation swap before TVL accumulated.
    • The attacker needed enough patience to plant the backdoor before launch, then wait until contract TVL justified exercising it.
  • Technical anatomy worth remembering:
    • The memorable part of Munchables is the sequence inversion: the attacker reportedly upgraded from malicious back to benign, not the more familiar benign-to-malicious flip at drain time.
    • That matters because proxy-upgrade monitoring that only watches for obviously malicious code at the moment of theft can miss an earlier preparation step where storage is poisoned and the contract is then made to look normal again.
    • In the post-incident published code, unlock() checks lockedTokens[msg.sender][_tokenContract].quantity before transferring assets. That means the malicious phase only needed to do one of two things: rewrite the attacker’s lock-accounting storage or temporarily replace the check with permissive logic. Public reporting points to the storage-rewrite path.
    • ProxyFactory inheriting TransparentUpgradeableProxy is a good reminder that upgrade frameworks are not inherently unsafe, but whoever holds admin over them is holding a de facto hot root key for every proxied balance and invariant.
    • This incident belongs in the same mental bucket as multisig compromise, but with one important twist: the theft authority was hidden in deployment architecture rather than presented openly as a treasury signer set.
  • Representative transactions and addresses:
    • Malicious upgrade/setup tx: 0xea1d9c0d8de4280b538b6fe6dbc3636602075184651dfeb837cb03f8a19ffc4f
    • Drain tx via unlock(address,uint256): 0x9a7e4d16ed15b0367b8ad677eaf1db6a2a54663610696d69e1b4aa1a08f55c95
    • Attacker address: 0x6e8836f050a315611208a5cd7e228701563d09c5
    • Return-funds txs: 0x69f271f90204ae993200f54676c922fe5ee3e5020a16ae34f589f52d923857f1, 0x381d57aa2d959ff9580ad61cc6549ae3c026eed9ee5b2ea10f9601a186c49a13, 0x62a148877957cbf1ae89cafa144496d99239ee900a3b90194249e6baaa3ddc2f
  • Response / mitigation:
    • The team publicly acknowledged the compromise and tracked the attacker’s fund movements in real time.
    • Community investigators, especially ZachXBT and quit.q00t.eth, rapidly tied the exploit to a rogue developer account cluster and narrowed the attacker identity surface enough to make laundering difficult.
    • Rather than a chain rollback, the immediate resolution came from the attacker surrendering keys and returning funds, after which Blast contributors helped secure the recovered assets in a multisig.
    • The durable lesson is not just vet developers better; it is treat deployment, upgrade-admin transfer, implementation verification, and timelocked governance as part of custody design.

Pattern tags

  • privileged-upgrade-abuse
  • signer-quorum-compromise
  • approval-by-address-mutable-code-execution
  • The attacker did not need to defeat unlock() as written on launch day; they only needed hidden authority over the proxy layer that could redefine what unlock() meant before users noticed.
  • Munchables is also the clean upgrade -> mutate storage -> upgrade back -> cash out later exemplar. The malicious code path was preparation, not the visible drain transaction.
  • This is a strong reminder that unverified implementation swaps are not merely observability problems. When pooled withdrawal entitlement lives in mutable storage, an unverified admin-controlled implementation is direct custody authority.

Three upgrade-authority promotions this incident collapsed

  1. A retained proxy-admin path still existed over live LockManager state

    • Munchables had a production transparent proxy whose admin could still replace implementation logic after launch.
    • That alone only meant a hidden root path was reachable. It did not yet have to mean whatever that path authored counted as canonical lock truth.
  2. Munchables still promoted that proxy-admin-authored code and storage into canonical lock entitlement truth

    • The temporary malicious implementation was able to mutate the storage that the later benign-looking unlock() path trusted when deciding who owned withdrawable ETH.
    • This was the decisive promotion: a hidden admin path did not just exist, it was allowed to publish the same money-good entitlement truth that users thought the public lock logic enforced.
  3. That accepted rewritten lock truth still retained full withdrawal authority after the implementation reverted

    • Once the attacker had poisoned storage and switched back to benign-looking code, the normal unlock(address,uint256) entry point still honored the forged balance as real claimable ETH.
    • The admin path existed, the system accepted its authored storage as canonical, and that accepted story still deserved full pooled-withdrawal authority were three different approvals, and Munchables collapsed them together.

Why the exploit worked

  • The decisive break happened above the application logic. LockManager entitlement was whatever the proxy-admin-controlled implementation and storage said it was, so retained admin power already outranked every user-visible invariant.
  • The attacker reportedly used a short-lived malicious implementation to rewrite the accounting state that unlock() later consumed. That mattered because the final withdrawal could then pass through a normal-looking entrypoint and a restored benign implementation, leaving the visible cash-out leg looking less exotic than the real exploit step.
  • Munchables also failed the production authority actually transferred test. The dangerous credential was not just a launch-time bootstrap convenience; it remained a live path to rewrite pooled ETH withdrawal rights after TVL had accumulated.
  • The missing controls were architectural rather than forensic: no enforced timelock, no independent veto window, no hard requirement that upgraded code be verified and reviewed before becoming authoritative, and no blast-radius reduction between admin changed code and admin-controlled state can withdraw user ETH.

Detection and audit pivots

  • Alert on any production implementation change as a custody event, not a routine deployment event. For Munchables, the key setup transaction happened five days before the public drain, which is exactly the window good monitoring should have been trying to buy defenders.
  • Treat upgrade back to something normal-looking as suspicious rather than reassuring when it follows an unverified implementation or unusual admin action. Restoration can be part of the exploit, not evidence that the danger passed.
  • Inventory contracts where entitlement is read from mutable storage and later honored by generic withdrawal paths. Those systems are especially exposed to transient malicious upgrades because poisoned storage can outlive the malicious code itself.
  • Ask whether the protocol can prove who currently owns every proxy admin, factory owner, and upgrade role, and whether that ownership matches the public security story. Hidden residual admin paths are a solvency issue, not an org-chart issue.
  • Review whether implementation verification, bytecode pinning, storage-layout diffing, and post-upgrade invariant checks are mandatory gates or merely best-effort habits. In this class of incident, optional review is functionally no review.

Failure-prevention checklist

  • Transfer production upgrade authority out of deployer or individual-developer control before meaningful TVL arrives, then verify the transfer onchain and in runbooks rather than treating it as an assumed social handoff.
  • Put implementation swaps, owner changes, and factory-admin mutations behind a delay, explicit bytecode commitment, and a separate emergency veto / pause path.
  • Refuse to treat unverified implementations as production-safe for custody-bearing contracts. If a live proxy points at code that independent reviewers cannot inspect, the protocol should behave as though custody is already in question.
  • Monitor for upgrade -> privileged state mutation -> upgrade back sequences explicitly. That pattern should page responders even if the final user-facing implementation looks benign.
  • Separate pooled asset custody from easily rewritable entitlement state where possible, or at minimum require post-upgrade invariant proofs that stored balances, lock records, and aggregate liabilities still reconcile.
  • Keep a machine-readable authority map for every proxy, factory, role manager, and emergency admin surface so responders can answer who can redefine withdrawal rights right now? without reconstructing architecture during the incident.

Audit prompts

  • Which exact role or contract can redefine withdrawal entitlement today: proxy admin, factory owner, implementation owner, role manager, or some inherited deployment path that the team no longer talks about?
  • Does every privileged change commit to exact implementation bytecode, initializer calldata, storage-layout expectations, and post-change invariants, or can signers approve a vague upgrade without pinning what becomes live?
  • What monitoring would have fired on a transient unverified implementation five days before a drain, and who could have paused or quarantined the system inside that window?
  • If a malicious implementation rewrote entitlement storage and then restored benign code, what reconciliation check would catch the poisoned balances before users or attackers called the ordinary withdrawal path?
  • Are production upgrade keys truly distinct from developer bootstrap keys in both custody practice and onchain reality, or is the separation mainly social?
  • What fraction of TVL can one captured admin action put at risk before any rate limit, delay, or pause authority meaningfully reduces loss?

Lessons that should already be standard

  • A retained proxy admin exists, that admin can still publish canonical entitlement truth, and that truth still unlocks pooled ETH are three different approvals.
  • Upgradeability is custody when it can rewrite who owns pooled assets. Treating proxy admin as mere maintenance authority is a category error.
  • The malicious code is no longer live is not a safety proof. If the attacker already rewrote entitlement state, the dangerous part may already be durable.
  • Production authority transfer needs a cryptographic and operational completion check, not an assumption that trusted staff or contractors will behave. Residual bootstrap authority is live risk until extinguished.
  • Unverified code on a custody-bearing proxy is an emergency condition, not a documentation nit. If no one outside the operator can inspect what became authoritative, the system has already crossed a dangerous trust boundary.
  • Detection needs to watch the preparation phase, not just the cash-out transaction. By the time a normal withdrawal call drains funds, the decisive failure may be days old.

Why this incident matters

  • Munchables is a clean example of upgradeability itself being the exploit surface. Users did not need to sign malicious approvals, trade against a broken oracle, or hit an obscure edge case; they only needed to trust a contract whose real admin boundary was different from what the UI implied.
  • It is also a useful reminder that insider-threat failures can be perfectly onchain in their final expression. The social failure happened offchain, but the actual theft path was an ordinary privileged state mutation followed by a standard withdrawal call.
  • In corpus terms, it fills an important gap between explicit multisig-compromise incidents and pure logic bugs: hidden upgrade authority can be just as terminal as stolen bridge keys.

Internal linkages

  • Core recurring pattern: privileged-upgrade-abuse.
  • Adjacent pattern: signer-quorum-compromise.
  • Adjacent pattern: approval-by-address-mutable-code-execution — useful when the sharper question is why a reviewed address or proxy wrapper was still allowed to hide mutable executable identity behind it.
  • Adjacent pattern: unprotected-initializer-reinitialization — both are upgradeability trust-boundary failures, but here the problem was retained admin authority rather than replayable setup code.
  • Positive-design companion: execution-envelope-identity-pinning — use this when the sharper design question is how upgrade approvals should have committed to exact implementation identity rather than a mutable proxy-controlled surface.
  • Positive-design companion: timelock-action-bundle-commitment — use this when the sharper design question is how an implementation change, its calldata, and the exact bytes becoming authoritative should have been delayed and reviewable as one committed bundle.

Control surface