57194 sc medium signature replay across collections missing contract binding

Submitted on Oct 24th 2025 at 09:17:57 UTC by @xKeywordx for Audit Comp | Belongarrow-up-right

  • Report ID: #57194

  • Report Type: Smart Contract

  • Report severity: Medium

  • Target: https://github.com/immunefi-team/audit-comp-belong/blob/main/contracts/v2/tokens/AccessToken.sol

  • Impacts:

    • Unauthorized minting of NFTs

Summary

AccessToken::mintStaticPrice and mintDynamicPrice rely on backend-signed payloads that are verified in SignatureVerifier.checkStaticPriceParameters / checkDynamicPriceParameters.

The signed hash includes:

  • receiver

  • tokenId

  • tokenUri

  • whitelisted for static or price for dynamic

  • block.chainid

Crucially, it does not include the collection's contract address, the factory address, or any unique collection identifier.

As a result, a signature produced for Collection A on chain X is also valid for Collection B on the same chain, as long as both collections use the same signerAddress stored in their Factory. A user can obtain a valid signature for one collection, and replay that signature to mint on a different collection, leading to unauthorized minting of NFTs.

Examples of consequences:

  • User whitelisted for Collection A (but not B) can mint on Collection B using the same signature.

  • If dynamic pricing differs across collections, a low-price signature for Collection A can be replayed on Collection B to mint at the lower price, causing revenue loss.

Root cause

The signed digest omits address(this) (the target collection that the signature is intended to be used with) or any unique collection identifier.

Impact

circle-exclamation

Consider adding a unique collection identifier to the signed payload (for example address(this) or the collection contract address) so signatures are bound to a specific collection.

Proof of Concept

The following test demonstrates signature replay across two deployed collections that share the same signer on the same chain.

Step overview:

1

Step — Obtain a signature for Collection A

The backend signer issues a "whitelisted" mint signature (note: the signed digest currently does not include the collection address).

Example (in the PoC test, this uses EthCrypto to compute the same digest shape as the contract expects):

2

Step — Use signature on Collection A (expected)

Use the signature to mint on Collection A (native ETH payment path). This succeeds as intended.

3

Step — Reuse same signature on Collection B (unexpected)

Reuse the exact same signature on Collection B (ERC20 payment path). This also succeeds, even though the signature was issued for Collection A and not intended for Collection B.

This results in unauthorized minting on Collection B.

Full test code used in the PoC (place inside the describe('Mint', () => { block in test/v2/tokens/accessToken.test.ts):

Test output:

-- End of report.

Was this helpful?