Reproduced Exploit

Ajna ExtraordinaryFunding — caller-supplied voter accounts drain the treasury

voteExtraordinary(account_, proposalId_) credits the nominated account's voting power rather than msg.sender. A contract can loop over every holder, reach the threshold, and execute a treasury transfer.

Apr 2023Othergovernance2 min read

Chain

Other

Category

governance

Date

Apr 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: 21301-extraordinary-proposal-can-be-used-to-steal-extraordinary-am. Standalone Foundry PoC and full write-up: 21301-extraordinary-proposal-steal-ajna_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/governance/proposal-manipulation · vuln/access-control/missing-owner-check

Reproduction: local synthetic Foundry reduction; the complete passing trace is in output.txt.

Key info#

FieldValue
LossOne caller fabricates 1,000 votes and transfers the entire modeled AJNA treasury.
Vulnerable contractExtraordinaryFunding.voteExtraordinary in test/21301-extraordinary-proposal-steal-ajna.sol
Attacker EOA0x1111111111111111111111111111111111111111
Attack contractExploit
Attack txLocal Foundry Exploit.run()
Chain · block · dateEthereum model · block 0 · synthetic
CompilerSolidity ^0.8.24
Bug classExtraordinary vote account not bound to caller

TL;DR#

voteExtraordinary(account_, proposalId_) credits the nominated account's voting power rather than msg.sender. A contract can loop over every holder, reach the threshold, and execute a treasury transfer.

Background#

Extraordinary proposals are intentionally fast and one-way. That makes correct voter identity binding essential; delegated voting power must be explicit and non-forgeable.

The vulnerable code#

SOLIDITY
function voteExtraordinary(address account, uint256 proposalId) external returns (uint256 votesCast) {
    require(!voted[proposalId][account], "already voted");
    // @> VULN: caller can nominate any account instead of msg.sender.
    voted[proposalId][account] = true;
    votesCast = votingPower[account];
}

Root cause#

The API treats account as an authenticated voter identity without requiring a delegation signature or account == msg.sender.

Preconditions#

  • Holders have voting power recorded in the grant fund.
  • The attacker can create an extraordinary proposal.
  • voteExtraordinary accepts arbitrary account arguments.

Attack walkthrough#

  1. Fund the grant with 1,000 AJNA and assign power to three holders.
  2. The attacker calls voteExtraordinary three times naming those holders.
  3. Threshold passes and executeExtraordinary transfers all AJNA; see output.txt:4.

Diagrams#

flowchart TD A[Attacker contract] -->|vote Holder A| G[GrantFund] A -->|vote Holder B| G A -->|vote Holder C| G G --> T{Threshold reached} T -->|execute| X[AJNA treasury drained]

Remediation#

Use msg.sender as the voter identity, or require a valid delegation signature tied to account. Add invariants that total votes cannot exceed caller-authorized voting power.

How to reproduce#

BASH
cd evm-hack-registry/21301-extraordinary-proposal-steal-ajna_exp
forge test -vvvvv

Sources#

Reference: https://github.com/trailofbits/publications/blob/master/reviews/2023-04-ajnalabs-securityreview.pdf


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.