Reproduced Exploit

ZKPanther (Base) — Reality.eth governance module → proxy upgrades → ZKP drain

1. Panther’s Base DAO is a Gnosis Safe with a Zodiac RealityModuleETH clone: anyone can addProposal, and a Reality.eth “yes” answer with only a 0.5 ETH bond becomes executable after 12h timeout + 8h cooldown if nobody counter-bonds “no”. 2. Attacker submitted proposal zkp-reexploit (2026-08-05) tha…

Aug 2026Basegovernance8 min read

Loss

~5.12M ZKP (~$15.6k at ~$0.003/ZKP) + ~0.123 ETH; team: Base zone not yet in production, no end-user funds

Chain

Base

Category

governance

Date

Aug 2026

Source

Crypto Training

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. Crypto Training original detection and analysis (live Twitter/X security-alert intake — not from DeFiHackLabs). Standalone Foundry PoC, offline anvil_state.json, and full write-up: 2026-08-ZKPantherGovernanceUpgrade_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/governance/proposal-manipulation · vuln/access-control/centralization · vuln/dependency/upgradeable-contract

Reproduction: offline Foundry project in . · canonical trace output.txt · governance-takeover test test/2026-08-ZKPantherGovernanceUpgrade_exp.sol (synthetic 2026-08-ZKPantherGovernanceUpgrade.sol)


Key info#

Loss~5.12M ZKP (~$15.6k at ~$0.003/ZKP) + ~0.123 ETH; team: Base zone not yet in production, no end-user funds
ChainBase (chainId 8453), fork block 49,625,945 (post CREATE upgrade/drain)
ProtocolPanther Protocol / ZKPanther (@ZKPanther) — Base deployment
Attacker EOA0x7dB4cFea…95B6
ZKP token (listed victim)0x0a776C1c…FdA4 (OptimismMintableERC20)
Panther Safe (avatar)0xb16283A2…0284
Reality module (clone)0x4ce69e77…51b4 → impl RealityModuleETH
Reality.eth oracle0x2F39f464…60e8
Drain helper0x9400161d…210c (attacker-deployed, unverified)
Funds sourcesZkpReserveController 0xEEA28c…CD45 (~3.84M ZKP), EIP173 proxies (e.g. 0x70c69b…A31e ~1.00M), PantherPoolV1 0x533583…e16C (~0.28M)
Proposal tx0xe6a25b20…1d5a — name zkp-reexploit (block 49,587,776)
Upgrade + pull CREATE0x88fb5398…7a01 (block 49,625,945)
Sweep tx0xead22569…332d (block 49,625,963)
AlertDefimonAlerts 2026-08-07
Bug classOptimistic Reality.eth DAO module: low bond (0.5 ETH) + 12h timeout + 8h cooldown, unchallenged “yes” → Safe executes malicious upgrades / drain; Base-specific disable-when-idle protections not enabled

TL;DR#

  1. Panther’s Base DAO is a Gnosis Safe with a Zodiac RealityModuleETH clone: anyone can addProposal, and a Reality.eth “yes” answer with only a 0.5 ETH bond becomes executable after 12h timeout + 8h cooldown if nobody counter-bonds “no”.
  2. Attacker submitted proposal zkp-reexploit (2026-08-05) that (among other steps) upgraded ZKP-holding EIP173 proxies to a drainer path and pulled inventory into an attacker helper.
  3. No honest party challenged. After cooldown, the module / payload executed; CREATE 0x88fb… moved ~5.124M ZKP into 0x9400…, then sweep() sent ZKP (+ residual ETH) to the attacker EOA.
  4. Team statement: Base deployment was not in production; protections meant to disable the Reality module when there is no active legitimate governance proposal had not been enabled on Base. Proxies were restored afterward.

Background#

Panther Protocol is a privacy-oriented DeFi stack. On Base they deployed a DAO-controlled zone with Reality.eth–based governance (see PIP-30 and related DAO posts). The operational pattern is the standard Zodiac Reality Module:

  • Safe owns proxies / controllers that hold protocol ZKP and related assets.
  • RealityModule is an enabled Safe module (getModulesPaginated includes 0x4ce69e…).
  • Module parameters on the clone (read on-chain):
ParamValue
avatar / targetPanther Safe 0xb162…
oracleReality.eth 0x2F39…
minimumBond0.5 ETH
questionTimeout43,200 s (12h)
questionCooldown28,800 s (8h)

That matches the DefimonAlerts description exactly. Optimistic oracles are safe only if monitoring + capital exist to counter-bond false answers within the window. An unattended pre-production deployment with multi-million-token inventory is a classic “governance surface left live” failure mode.


The vulnerable code#

1. RealityModule — execute once Reality returns “yes”#

Verified source: sources/RealityModuleETH_4e35DA/contracts_RealityModule.sol

SOLIDITY
// executeProposalWithIndex — approval check
require(
    oracle.resultFor(questionId) == bytes32(uint256(1)),
    "Transaction was not approved"
);
// … bond / cooldown / expiration checks …
require(exec(to, value, data, operation), "Module transaction failed");

There is no extra “is this proposal on an allowlist / is governance in an active season?” gate in the module itself. That layer was supposed to be operational / deployment-specific and was missing on Base.

2. EIP173Proxy — owner can swap implementation#

Verified source: sources/EIP173ProxyWithReceive_70c69b/…/EIP173Proxy.sol

SOLIDITY
function upgradeTo(address newImplementation) external onlyOwner {
    _setImplementation(newImplementation, "");
}

function upgradeToAndCall(address newImplementation, bytes calldata data)
    external
    payable
    onlyOwner
{
    _setImplementation(newImplementation, data);
}

owner() of the EIP173 proxies is the Panther Safe. Once the Reality module can exec as the Safe, upgradeTo / upgradeToAndCall become attacker-reachable.

3. The reproduced takeover (PoC)#

The Foundry test + EVM Playground reproduce the root-cause chain, not just the final sweep, with the real vulnerable patterns wired end-to-end:

  1. RealityModule.executeProposalWithIndex — the exec gate above, reproduced verbatim: the ONLY approval check is an unchallenged Reality "yes" (0.5 ETH bond), with no allowlist on the proposal's (to, data).
  2. Safe.execTransactionFromModule — the enabled module execs the attacker's tx as the Safe.
  3. EIP173Proxy.upgradeToAndCall (onlyOwner = Safe) — the Safe swaps a ZKP-holding proxy's implementation to the attacker's drainer and delegatecalls its init data.
  4. Drainer.drain — running in the proxy's context, transfers the proxy's entire 5,124,773.626006184526790998 ZKP to the attacker EOA.

Multi-day Reality bonding timing is abstracted (module cooldown 0, oracle pre-finalized "yes") — the historical 12h+8h challenge window cannot be re-simulated in a single replay — but every access-control step (module exec gate, Safe module-exec, EIP-173 upgradeToAndCall, delegatecall drain) runs unmodified. The original on-chain attacker helper 0x9400… sweep() (historical 0xead225…) was only the last mile of exactly this chain.


Root cause#

  1. Optimistic governance without active defense. Reality modules are designed for disputable proposals. With 0.5 ETH bond and a ~20h finalize path, an unmonitored deployment is free real estate for a malicious multi-call proposal.
  2. Missing Base “idle disable” protections. Team post-mortem (Discord via DefimonAlerts image): protections that should disable the Reality module when there is no active governance proposal were not turned on for the Base deployment.
  3. Privilege concentration. The same Safe that owns upgradeable proxies / reserve controllers is the Reality module’s avatar — so a single unchallenged “yes” equals full proxy admin.

This is not a Solidity bug in RealityModule’s core math; it is a governance / deployment configuration failure that turns a legitimate module into a permissionless upgrade oracle.


Preconditions#

  • RealityModule enabled on the Panther Safe with low minimumBond and multi-hour timeout/cooldown.
  • Module not disabled / guarded for idle periods on Base.
  • Proxies / reserve controllers hold large ZKP balances and are Safe-owned upgradeable.
  • No counter-bond within the challenge window.

Attack walkthrough#

sequenceDiagram participant A as Attacker EOA participant RM as RealityModule clone participant O as Reality.eth oracle participant S as Panther Safe participant P as EIP173 proxies / Reserve participant H as Drain helper 0x9400 A->>RM: addProposal "zkp-reexploit" (upgrade + drain txs) A->>O: answer YES + 0.5 ETH bond Note over O: 12h timeout, no counter-bond, +8h cooldown A->>RM: executeProposal / CREATE payload RM->>S: exec as module S->>P: upgradeTo(drainer) / pull ZKP P->>H: ~5.124M ZKP A->>H: sweep() H->>A: 5.124M ZKP + ~0.123 ETH

Timeline (Base)#

Time (UTC)BlockEvent
2026-08-05 21:2049,587,737Attacker deploys drain helper 0x9400…
2026-08-05 21:2149,587,776initiate / Reality proposal zkp-reexploit (LogNewQuestion + ProposalQuestionCreated)
2026-08-06 18:3349,625,945CREATE 0x88fb… upgrades proxies & pulls ~5.124M ZKP into helper
2026-08-06 18:3449,625,963sweep() → attacker EOA

PoC numbers (output.txt)#

CODE
Helper ZKP before sweep: 5124773.626006184526790998
Attacker ZKP profit:     5124773.626006184526790998

Also observed in the trace: helper forwards residual ETH ~0.123291500437368375 to the attacker during sweep().

PoC scope. The PoC reproduces the governance takeover itself — unchallenged Reality "yes" → module exec as the Safe → upgradeToAndCall(drainer) → drain — using the real vulnerable patterns (module exec gate, Safe module-exec, EIP-173 upgrade, delegatecall drain). Only the multi-day bonding timing is abstracted (cooldown 0, pre-finalized "yes"); every access-control step runs unmodified.


Diagrams#

flowchart TB subgraph governance [Optimistic governance] Prop[Proposal zkp-reexploit] Yes[Reality YES bond 0.5 ETH] Wait[12h + 8h no challenge] Prop --> Yes --> Wait end subgraph power [Safe authority] Safe[Panther Safe] RM[RealityModule] RM -->|exec| Safe end subgraph assets [Upgrade + drain] Proxies[EIP173 proxies / Reserve / Pool] Helper[Drain helper] Attacker[Attacker EOA] Safe -->|upgradeTo / pull| Proxies Proxies --> Helper Helper -->|sweep| Attacker end Wait --> RM

Remediation#

  1. Disable or tightly guard Reality modules on non-production / low-watch deployments; enable the “no active proposal → module off” controls the team described.
  2. Raise bonds / lengthen cooldowns proportional to TVL under the Safe; require multi-sig or guardian counter-bond automation (PagerDuty / bots watching Reality questions).
  3. Separate upgrade admin from optimistic modules: timelock + guardian for upgradeTo, or immutable implementations for vaults holding inventory.
  4. Allowlist proposal targets / selectors in a custom module guard so a Reality “yes” cannot call arbitrary upgradeToAndCall.
  5. Monitor ProposalQuestionCreated, Reality LogNewAnswer, and Safe ExecutionFromModuleSuccess with auto-challenge playbooks.

How to reproduce#

The governance-takeover PoC is self-contained (local deploy, no fork or RPC needed):

BASH
cd 2026-08-ZKPantherGovernanceUpgrade_exp
forge test -vv

Expect both tests green:

  • test_governanceTakeover_drainsViaRealityModule~5,124,773.626 ZKP drained to the attacker EOA via addProposal → unchallenged Reality "yes" → executeProposal → Safe upgradeToAndCall(proxy → drainer) → drain, and the proxy's implementation slot is asserted to now point at the drainer.
  • test_control_noApprovalNoExec — without an approved Reality "yes", the module's exec gate reverts ("Transaction was not approved"); no upgrade, no drain.

The same synthetic drives the in-browser EVM Playground (opcode-level replay + marked source lines) at /hacks/2026-08-ZKPantherGovernanceUpgrade/.


Reference: https://x.com/DefimonAlerts/status/2085673531409400047


Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

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.