Reproduced Exploit

Heurist — mint pays the referral fee to any address

mint charges 1 ETH and forwards 0.1 ETH (10 percent) to the referral address. That address is not checked. It can be the minter, address(0), or any other account.

Jul 2024•Other•input-validation•4 min read

Chain

Other

Category

input-validation

Date

Jul 2024

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: 37690-insufficient-validation-of-referral-address-in-mint-zokyo-no. Standalone Foundry PoC and full write-up: 37690-insufficient-validation-of-referral-address-in-mint-zokyo-no_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/input-validation/missing · vuln/logic/fee-calculation

Reproduction: a self-contained Foundry PoC that compiles and runs offline with only forge-std — no fork and no RPC. Trace: output.txt. PoC: test/37690-insufficient-validation-of-referral-address-in-mint-zokyo-no_exp.sol.


Key info#

ImpactHIGH — a self-referral keeps the 0.1 ETH referral fee. The authorized referrer receives 0. The checked mint pays that 0.1 ETH to the real referrer and rejects the caller and address(0)
ProtocolHeurist ZkImagine.mint
Vulnerable codemint forwards the referral fee to the passed address with no check
Bug classReferral address is not validated
FindingZokyo — Heurist, 1 July 2024 · id 37690
ReportZokyo Heurist, 2024-07-01
SourceAuditVault finding
StatusAcknowledged. Reproduced here as a local synthetic.
Compilersolc 0.8.27, cancun, via IR

TL;DR#

mint charges 1 ETH and forwards 0.1 ETH (10 percent) to the referral address. That address is not checked. It can be the minter, address(0), or any other account.

The minter passes their own address. The contract sends the 0.1 ETH referral fee back to them. The authorized referrer’s balance stays 0. The contract keeps the other 0.9 ETH, and one NFT is minted.

The control rejects the caller and address(0), requires an authorized referrer, and pays that referrer the same 0.1 ETH.

run() does not warp. The 1 ETH is msg.value on the single call.

The vulnerable code#

SOLIDITY
function mint(address referral) external payable {
    require(msg.value == MINT_FEE, "fee");
    // @> VULN: referral is not checked, so the minter can pass their own address
    (bool ok,) = referral.call{value: REFERRAL_FEE}("");
    require(ok, "pay");
    minted[msg.sender] += 1;
}

MINT_FEE is 1 ETH. REFERRAL_FEE is 0.1 ETH. An authorized referrer is stored and never read.

The fixed mint reverts unless referral is nonzero, is not the caller, and is authorized. The same 0.1 ETH is then sent to that referrer.

Root cause#

The discount is implemented as a payment to an address chosen by the buyer. Nothing ties that address to a referrer the protocol actually registered, and nothing stops the buyer from naming themselves.

Preconditions#

  • Mint fee is 1 ETH. The referral cut is 0.1 ETH.
  • An authorized referrer exists. mint does not consult that flag.
  • The caller can receive ETH.
  • No timestamp change happens inside run().

Attack walkthrough#

  1. The minter calls mint with 1 ETH and their own address.
  2. The fee check passes. The referral is not compared to the caller or to the authorized referrer.
  3. 0.1 ETH is sent to the minter. The contract keeps 0.9 ETH.
  4. The authorized referrer’s balance is 0. One NFT was minted.

Control: mint(minter) and mint(address(0)) revert. mint(authorized) pays 0.1 ETH to that referrer and leaves 0.9 ETH in the contract.

Diagrams#

flowchart TD A["Minter pays 1 ETH and passes their own address"] --> B["mint does not check the referral"] B --> C["0.1 ETH is sent back to the minter"] C --> D["The authorized referrer receives 0"] D --> E["The contract keeps 0.9 ETH"] E --> F["A checked mint pays that 0.1 ETH to the authorized referrer"]

Impact#

The 0.1 ETH referral fee is paid to the minter. The authorized referrer receives nothing. The same mint, with the check, pays that 0.1 ETH to the referrer and refuses the caller and address(0).

Remediation#

Reject address(0) and the caller. Pay the referral fee only to an authorized referrer.

Sources#

Taxonomy#

  • severity/high
  • vuln/input-validation/missing
  • vuln/logic/fee-calculation

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.