Reproduced Exploit

NFTMirror — Anyone can re-mint a token that was burned by the owner

1. Non-existent tokens default to locked, so only the beacon can mint them. 2. Owner can burn an unlocked token; burn does not set lock state back to locked. 3. After burn the id stays unlocked → anyone can call mint(to, tokenId) successfully. 4. Fix: _setExtraData(tokenId, LOCKED) after owner burn.

Dec 2024Otheruntagged3 min read

Chain

Other

Category

untagged

Date

Dec 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: 50036-c-01-anyone-can-re-mint-a-token-that-was-burned-by-the-owner. Standalone Foundry PoC and full write-up: 50036-c-01-anyone-can-re-mint-a-token-that-was-burned-by-the-owner_exp in the evm-hack-registry mirror.


Vulnerability classes: access-roles · specific-token-type

Reproduction: self-contained Foundry PoC with only forge-std — no fork. Full trace: output.txt. PoC: test/50036-c-01-anyone-can-re-mint-a-token-that-was-burned-by-the-owner_exp.sol.


Key info#

ImpactHIGH — burned shadow NFTs can be re-minted by anyone, breaking burn finality
ProtocolNFTMirror — NFTShadow
Vulnerable codeburn() unlocked branch does not re-lock; _beforeTokenTransfer only gates locked ids
Bug classMissing state reset after burn / access control hole
FindingPashov Audit Group · NFTMirror-security-review 2024-12-30 · C-01
Reportpashov/audits NFTMirror
SourceAuditVault
StatusAudit finding. Reproduced as a standalone local synthetic.
Compiler^0.8.24 (PoC)

TL;DR#

  1. Non-existent tokens default to locked, so only the beacon can mint them.
  2. Owner can burn an unlocked token; burn does not set lock state back to locked.
  3. After burn the id stays unlocked → anyone can call mint(to, tokenId) successfully.
  4. Fix: _setExtraData(tokenId, LOCKED) after owner burn.

The vulnerable code#

SOLIDITY
function burn(uint256 tokenId) external {
    if (tokenIsLocked(tokenId)) {
        _burn(tokenId);
    } else {
        // @> VULN: burn leaves the token unlocked — anyone can mint again
        _burn(msg.sender, tokenId);
        // FIX: _setExtraData(tokenId, LOCKED);
    }
}
SOLIDITY
function _beforeTokenTransfer(...) internal view {
    if (msg.sender != BEACON_CONTRACT_ADDRESS) {
        if (tokenIsLocked(tokenId)) revert CallerNotBeacon();
    }
}

Root cause#

Mint gating relies on the default-locked status of token ids. Burn of an unlocked token clears ownership but leaves the unlock flag, so the gate no longer applies to the burned id.


Preconditions#

  • Token was unlocked (beacon unlock path).
  • Owner burns it.
  • Attacker calls mint on the same id.

Attack walkthrough#

  1. Beacon mints token 8903 to owner and unlocks it.
  2. Owner burns 8903tokenIsLocked(8903) == false still.
  3. Attacker mints 8903 to themselves → owns the "burned" id.

Diagrams#

sequenceDiagram participant Beacon participant Shadow as NFTShadow participant Owner participant Attacker Beacon->>Shadow: mint + unlock Owner->>Shadow: burn unlocked token Note over Shadow: extraData stays UNLOCKED Attacker->>Shadow: mint same tokenId Shadow-->>Attacker: success — re-minted

Impact#

Burn is not final; arbitrary callers can resurrect burned shadow NFTs.


Taxonomy#

  • genome: access-roles, specific-token-type
  • severity/high · sector/bridge · Pashov Audit Group

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.