Sandbox Land public burn flaw enables unauthorized NFT destruction
Exploit Transactions
0x498b681a6b691dbbccae0671f6d2d0848aa9f2eba3db0dc9dd2806377aaa8991Victim Addresses
0x50f5474724e0ee42d9a4e711ccfb275809fd6d4aEthereumLoss Breakdown
Similar Incidents
NFTX Doodles collateral accounting flaw enables flash-loan ETH extraction
33%Unauthorized WETH drain via unprotected Uniswap V3 callback
32%WIFStaking claimEarned bug enables repeated WIF reward extraction
30%SorraV2 staking withdraw bug enables repeated SOR reward drain
29%PLNTOKEN transferFrom burn hook drains WETH reserves
29%LiteV3 Bridge Aggregator Proxy Initialization Race Enabled Unauthorized UUPS Takeover
29%Root Cause Analysis
Sandbox Land public burn flaw enables unauthorized NFT destruction
1. Incident Overview TL;DR
The Sandbox Land ERC-721 contract on Ethereum exposes a public _burn(address from, address owner, uint256 id) function that lacks any msg.sender or approval checks. At least three on-chain transactions demonstrate that non-owner EOAs can call _burn(victim, victim, id) to irreversibly burn LAND tokens owned by other addresses, resulting in unauthorized destruction of user NFTs.
2. Key Background
The Sandbox Land contract implements an ERC-721-style NFT representing LAND parcels, with standard semantics that only token owners or their approved operators should be able to transfer or burn tokens. In the Land implementation, high-level user-facing burn functions are burn(uint256 id) and burnFrom(address from, uint256 id). These functions enforce ownership and operator checks using _ownerOf(id), _ownerAndOperatorEnabledOf(id), metaTransactionContracts, per-token operators, superOperators, and operatorsForAll, before delegating to an internal burn routine. The shared burn routine is implemented as ERC721BaseToken._burn(address from, address owner, uint256 id). In a secure design this function would be internal only and rely on its callers to supply a correct from/owner pair after authorization; however, in this contract _burn is declared public and is exposed externally through the Land ABI. Because _burn is public and only checks from == owner, any EOA can construct calldata that passes this check by setting from == owner == the current ownerOf(id) and thereby induce a burn for any publicly known LAND token id, regardless of who actually controls that owner address.
3. Vulnerability Analysis & Root Cause Summary
The root cause is a public access-control vulnerability: ERC721BaseToken._burn(address from, address owner, uint256 id) is declared public and exposed in the Land ABI, but it enforces only from == owner and performs no msg.sender or approval checks, allowing any unprivileged EOA to directly burn LAND tokens they do not own by calling _burn(victim, victim, id).
Invariant: For any LAND token id, a state transition from an owned state (ownerOf(id) == v != address(0) and _owners[id] not equal to the burn sentinel) to a burned state (ownerOf(id) == address(0) and _owners[id] == 2**160, with an ERC-721 Transfer(v, address(0), id) event) must only be reachable via code paths that enforce that msg.sender is either (a) the token owner v, (b) an approved per-token operator for id, (c) a globally approved operator for v, or (d) a designated meta-transaction contract acting on behalf of v, under the checks implemented in burn and burnFrom.
Breakpoint: In ERC721BaseToken.sol, function _burn(address from, address owner, uint256 id) is declared public and implemented as require(from == owner, "not owner"); _owners[id] = 2**160; _numNFTPerAddress[from]--; emit Transfer(from, address(0), id); and Land.sol exposes this function in its external ABI. Because _burn never references msg.sender, _ownerOf(id), _ownerAndOperatorEnabledOf(id), or any approval mappings, an external caller can supply from == owner == current ownerOf(id) and cause a burn even when msg.sender is not an owner or approved operator, violating the invariant.
function _burn(address from, address owner, uint256 id) public {
require(from == owner, "not owner");
_owners[id] = 2**160; // cannot mint it again
_numNFTPerAddress[from]--;
emit Transfer(from, address(0), id);
}
Caption: ERC721BaseToken._burn implementation from the verified Land contract, showing the lack of any msg.sender or approval checks while exposing the function as public.
4. Detailed Root Cause Analysis
The Land contract is built atop ERC721BaseToken, which defines a shared burn routine _burn(address from, address owner, uint256 id). The intended design is that user-facing methods enforce access control before calling this internal routine. Indeed, burn(uint256 id) and burnFrom(address from, uint256 id) enforce that msg.sender is either the token owner, a meta-transaction contract, a per- token operator when operatorEnabled is true, a superOperator, or an operatorForAll entry, and only then delegate to _burn. However, ERC721BaseToken._burn is mistakenly declared public rather than internal, and Land.sol includes this function in its ABI. As a result, the function is callable directly from arbitrary EOAs. At the code level, _burn implements only require(from == owner, "not owner"); then writes _owners[id] = 2160 (a sentinel value indicating that the id can never be minted again), decrements _numNFTPerAddress[from], and emits Transfer(from, address(0), id). It never verifies that (from, owner) corresponds to the current ownerOf(id), and crucially it never checks msg.sender or any approval/role mapping. Because ownerOf(id) is publicly observable from on- chain state, an adversary can, at any block where a victim v owns a given LAND id, construct calldata _burn(v, v, id) that passes the only require and deterministically burns the token. The representative exploit tx 0x498b681a6b691dbbccae0671f6d2d0848aa9f2eba3db0dc9dd2806377aaa8991 at block 14164047 exemplifies this. The Land burn scan shows that at blockNumber-1, ownerOf(16653) == 0x2cf80a3efe4119d8e4ee3345267bc72b68aa5649, while tx_from == 0x794151bccaecc44a05e5d411293e5b8ad804258c. The tx's calldata encodes _burn(0x2cf8...5649, 0x2cf8...5649, 16653). The debug trace shows a single CALL into the Land contract executing _burn, which checks from == owner, writes _owners[16653] = 2160, decrements _numNFTPerAddress[0x2cf8...5649], and emits Transfer(0x2cf8...5649, address(0), 16653). No logic in _burn inspects msg.sender or any approval flags, and no other internal calls are made. Thus, the invariant that only owner/approved actors can trigger burns is broken exactly at the public _burn entrypoint.
{
"from": "0x794151bccaecc44a05e5d411293e5b8ad804258c",
"to": "0x50f5474724e0ee42d9a4e711ccfb275809fd6d4a",
"input": "0x6ee678ae0000000000000000000000002cf80a3efe4119d8e4ee3345267bc72b68aa56490000000000000000000000002cf80a3efe4119d8e4ee3345267bc72b68aa5649000000000000000000000000000000000000000000000000000000000000410d",
"type": "CALL"
}
Caption: Seed transaction trace for tx 0x498b681a6b691dbbccae0671f6d2d0848aa9f2eba3db0dc9dd2806377aaa8991, showing a direct CALL from adversary EOA 0x7941...258c to the Land contract with calldata encoding _burn(victim, victim, 16653).
5. Adversary Flow Analysis
Single-transaction exploit where an unprivileged EOA directly calls the publicly exposed Land._burn(address,address,uint256) function with from == owner == victim and id equal to a LAND token that victim currently owns, thereby bypassing burn/burnFrom authorization logic and burning the token in one step.
Adversary-related accounts:
- Adversary EOA 0x794151bccaecc44a05e5d411293e5b8ad804258c on Ethereum mainnet (chainid 1): Externally owned account that sends transaction 0x498b681a6b691dbbccae0671f6d2d0848aa9f2eba3db0dc9dd2806377aaa8991 directly to Land._burn(address,address,uint256) with from == owner equal to the pre-state ownerOf(16653) (0x2cf8...5649) while not being that owner; this EOA thus realizes the ACT opportunity by executing the unauthorized burn pattern.
Victim accounts and tokens:
- Unknown LAND holder (token 16653) at 0x2cf80a3efe4119d8e4ee3345267bc72b68aa5649 on Ethereum mainnet (chainid 1).
- Unknown LAND holder (token 3738) at 0x9cfa73b8d300ec5bf204e4de4a58e5ee6b7dc93c on Ethereum mainnet (chainid 1).
- Unknown LAND holder (token 99024) at 0x371f4c6fd305c6772bc6224b795b0b46b6b6f8db on Ethereum mainnet (chainid 1).
- The Sandbox Land contract at 0x50f5474724e0ee42d9a4e711ccfb275809fd6d4a on Ethereum mainnet (chainid 1).
Lifecycle stages:
- Pre-state: victim owns LAND token 16653: Effect: By block 14164046, Land.ownerOf(16653) equals 0x2cf80a3efe4119d8e4ee3345267bc72b68aa5649, and the token is live and not burned. This establishes the victim holding required for the exploit predicate. Evidence: artifacts/root_cause/data_collector/iter_4/other/1/land_burn_scan_with_owner_checks.json (owner_of_at_prev_block for tokenId 16653), plus prior LAND transfer and mint logs in land_txlist_full.json that show the token's ownership history.
- Adversary executes direct _burn call: Effect: EOA 0x794151bccaecc44a05e5d411293e5b8ad804258c submits a transaction to Land._burn(0x2cf8...5649, 0x2cf8...5649, 16653) with zero ETH value. The Land contract executes the public _burn function, checks only from == owner (which holds by construction), sets _owners[16653] to the burn sentinel, decrements _numNFTPerAddress[0x2cf8...5649], and emits Transfer(0x2cf8...5649, address(0), 16653), thereby irreversibly burning the victim's LAND token. Evidence: artifacts/root_cause/data_collector/iter_4/other/1/land_txlist_full.json (tx metadata for 0x498b...8991), artifacts/root_cause/data_collector/iter_4/other/1/land_burn_scan_with_owner_checks.json (sender_is_owner == false and owner_matches_arg == true), artifacts/root_cause/data_collector/iter_4/tx/1/0x498b681a6b691dbbccae0671f6d2d0848aa9f2eba3db0dc9dd2806377aaa8991/trace.debug_call.json (CALL into Land._burn with no further internal calls), and ERC721BaseToken._burn implementation.
- Post-state: victim's token destroyed and cannot be reminted: Effect: After execution of the exploit transaction, LAND token 16653 no longer has a valid owner (ownerOf(16653) returns address(0)), the internal _owners[16653] mapping entry is set to a sentinel that prevents reminting, and the victim's LAND balance _numNFTPerAddress[0x2cf8...5649] is reduced by 1. The harm is permanent loss of the NFT for the victim. Evidence: ERC721BaseToken._burn implementation in ERC721BaseToken.sol and the Transfer(0x2cf8...5649, address(0), 16653) log recorded in artifacts/root_cause/data_collector/iter_4/tx/1/0x498b681a6b691dbbccae0671f6d2d0848aa9f2eba3db0dc9dd2806377aaa8991/trace.debug_call.json and land_txlist_full.json.
6. Impact & Losses
- LAND: >=3 NFTs burned (tokenIds 3738, 16653, 99024) via the public _burn pattern where sender_is_owner == false The immediate on-chain impact is irreversible destruction of at least three LAND NFTs owned by distinct victim addresses, with no compensating mint or transfer. For each affected tokenId, the victim's LAND balance decreases and the token is marked as permanently burned (_owners[id] = 2**160), precluding any future reminting. The primary harm is thus non-monetary: victims lose unique NFT positions that may carry significant off-chain economic or utility value (e.g., marketplace value or in-game rights). Additional monetary effects, such as lost secondary-market value or reduced utility, are outside the scope of this strictly on-chain analysis but follow directly from the unauthorized burns.
7. References
[1] Land contract source - ERC721BaseToken._burn implementation — artifacts/root_cause/data_collector/iter_1/contract/1/0x50f5474724e0ee42d9a4e711ccfb275809fd6d4a/source/src/src/Land/erc721/ERC721BaseToken.sol [2] Land.sol and compiled ABI exposing _burn(address,address,uint256) — artifacts/root_cause/data_collector/iter_1/contract/1/0x50f5474724e0ee42d9a4e711ccfb275809fd6d4a/source/out/Land.sol/Land.json [3] Land _burn scan with ownerOf@prev-block and sender_is_owner classification — artifacts/root_cause/data_collector/iter_4/other/1/land_burn_scan_with_owner_checks.json [4] Full Land transaction list including all _burn calls — artifacts/root_cause/data_collector/iter_4/other/1/land_txlist_full.json [5] Debug trace for representative exploit tx 0x498b681a6b691dbbccae0671f6d2d0848aa9f2eba3db0dc9dd2806377aaa8991 — artifacts/root_cause/data_collector/iter_4/tx/1/0x498b681a6b691dbbccae0671f6d2d0848aa9f2eba3db0dc9dd2806377aaa8991/trace.debug_call.json [6] Seed LAND _burn transaction 0x34516ee081c221d8576939f68aee71e002dd5557180d45194209d6692241f7b1 and associated artifacts — artifacts/root_cause/seed/1/0x34516ee081c221d8576939f68aee71e002dd5557180d45194209d6692241f7b1