# GroupBuy may purchase NFT not in the allowed list

GroupBuy.purchase with empty purchase proof compares raw tokenId bytes to merkleRoot, letting a tokenId equal to the root bypass the allowlist.

- Payout: Contest reward
- Severity: medium
- Program: Tessera
- Credited: gzeon
- Disclosed: 2023-04-18
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2022-12-tessera-findings/issues/14
- Library page: https://bugbountylibrary.com/r/c4%3A2022-12-tessera%3AM-01
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:medium tech:solidity technique:source-review vuln:access-control vuln:business-logic

**Target:** GroupBuy.sol purchase allowlist merkle verification
**Root cause:** When _purchaseProof.length == 0, the code compares bytes32(_tokenId) directly against merkleRoot instead of hashing the tokenId, so a tokenId whose raw bytes equal the root passes verification without being in the allowlist.
**Steps:**
1. Deploy GroupBuy with a merkleRoot allowlist of NFTs.
2. Call purchase with _purchaseProof = new bytes32[](0) and _tokenId = uint256(merkleRoot).
3. Verification passes because bytes32(_tokenId) == merkleRoot, purchasing an NFT not in the allowlist.
**How found:** Code reading of the empty-proof branch in GroupBuy.purchase revealed the tokenId is compared to the root without hashing, unlike the normal proof path.
**Exploitation:** Attacker submits a purchase with an empty proof and tokenId set to the merkleRoot value, bypassing the allowlist to acquire an unapproved NFT through the group buy.
**Insight:** When a merkle verification has a special-case branch for empty proofs, check whether the leaf is hashed consistently with the non-empty path; a raw-vs-hashed mismatch lets an attacker pick a leaf equal to the root.
