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.
Chain
Other
Category
untagged
Date
Dec 2024
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
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.
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-registrymirror.
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#
| Impact | HIGH — burned shadow NFTs can be re-minted by anyone, breaking burn finality |
| Protocol | NFTMirror — NFTShadow |
| Vulnerable code | burn() unlocked branch does not re-lock; _beforeTokenTransfer only gates locked ids |
| Bug class | Missing state reset after burn / access control hole |
| Finding | Pashov Audit Group · NFTMirror-security-review 2024-12-30 · C-01 |
| Report | pashov/audits NFTMirror |
| Source | AuditVault |
| Status | Audit finding. Reproduced as a standalone local synthetic. |
| Compiler | ^0.8.24 (PoC) |
TL;DR#
- Non-existent tokens default to locked, so only the beacon can mint them.
- Owner can burn an unlocked token; burn does not set lock state back to locked.
- After burn the id stays unlocked → anyone can call
mint(to, tokenId)successfully. - Fix:
_setExtraData(tokenId, LOCKED)after owner burn.
The vulnerable code#
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);
}
}
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
minton the same id.
Attack walkthrough#
- Beacon mints token
8903to owner and unlocks it. - Owner burns
8903→tokenIsLocked(8903) == falsestill. - Attacker mints
8903to themselves → owns the "burned" id.
Diagrams#
Impact#
Burn is not final; arbitrary callers can resurrect burned shadow NFTs.
Taxonomy#
genome: access-roles,specific-token-typeseverity/high·sector/bridge· Pashov Audit Group
Sources#
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 50036-c-01-anyone-can-re-mint-a-token-that-was-burned-by-the-owner_exp (evm-hack-registry mirror).
- AuditVault finding: 50036-c-01-anyone-can-re-mint-a-token-that-was-burned-by-the-owner.
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.