57796 sc medium signature hashing collision in signatureverifier lets attacker deploy forged accesstoken credittoken metadata critical unintended alteration of what the nft represents

Submitted on Oct 28th 2025 at 22:51:15 UTC by @Codexstar for Audit Comp | Belongarrow-up-right

  • Report ID: #57796

  • Report Type: Smart Contract

  • Report severity: Medium

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

  • Impacts: Unintended alteration of what the NFT represents (e.g. token URI, payload, artistic content)

Description

Brief / Intro

The platform authorizes collection deployments via signatures checked in SignatureVerifier. These hashes are built with abi.encodePacked over multiple dynamic strings, which is ambiguous. Different (name, symbol, contractURI) (or (name, symbol, uri)) tuples can collide to the same bytes, so a valid signature issued for one tuple can be replayed to deploy a collection with different, forged metadata. This allows unauthorized alteration of what the NFT represents (branding, symbol, contract URI), meeting the Critical impact category.

Vulnerability Details

  • The authorization hashing for AccessToken and CreditToken uses abi.encodePacked across multiple dynamic strings:

// contracts/v2/utils/SignatureVerifier.sol (around line 53)
function checkAccessTokenInfo(address signer, AccessTokenInfo memory accessTokenInfo) external view {
    require(
        bytes(accessTokenInfo.metadata.name).length > 0 && bytes(accessTokenInfo.metadata.symbol).length > 0,
        EmptyMetadata(accessTokenInfo.metadata.name, accessTokenInfo.metadata.symbol)
    );

    require(
        signer.isValidSignatureNow(
            keccak256(
                abi.encodePacked(
                    accessTokenInfo.metadata.name,
                    accessTokenInfo.metadata.symbol,
                    accessTokenInfo.contractURI,
                    accessTokenInfo.feeNumerator,
                    block.chainid
                )
            ),
            accessTokenInfo.signature
        ),
        InvalidSignature()
    );
}
  • Factory trusts those verifications and proceeds with deployment:

  • Why exploitable: abi.encodePacked concatenates dynamic strings without boundaries. Example: "abc"||"x"||"" equals "ab"||"cx"||"". So a signature for (name='abc', symbol='x', contractURI='') also verifies for (name='ab', symbol='cx', contractURI=''). The factory then deploys the collection with the forged metadata.

Impact Details

  • Matches in-scope Critical impact “Unintended alteration of what the NFT represents (e.g. token URI, payload, artistic content)”.

  • Attacker can deploy unauthorized collections with altered name, symbol, contractURI/uri.

  • Consequences: brand spoofing, user confusion, fraudulent collections appearing authorized, downstream marketplace/dapp trust issues.

References

  • contracts/v2/utils/SignatureVerifier.sol:53

  • contracts/v2/utils/SignatureVerifier.sol:81

  • contracts/v2/platform/Factory.sol:223

  • contracts/v2/platform/Factory.sol:268

Proof of Concept

1

Step 1 — Obtain a legitimate platform signature for tuple A

Example for AccessToken: (name='abc', symbol='x', contractURI='', feeNumerator=f, chainId).

2

Step 2 — Craft tuple B that collides under packed encoding

Example: (name='ab', symbol='cx', contractURI='', feeNumerator=f, chainId) because abi.encodePacked('abc','x','',f,chainId) == abi.encodePacked('ab','cx','',f,chainId).

3

Step 3 — Call Factory.produce with tuple B and reuse signature for tuple A

Use the signature issued for tuple A but submit tuple B to the factory.

4

Step 4 — Signature verification passes and the factory deploys the collection

SignatureVerifier.checkAccessTokenInfo passes due to the collision; the factory deploys a collection with forged (name, symbol, contractURI).

5

Step 5 — The same applies to CreditToken

The same technique works against Factory.produceCreditToken by forging (name, symbol, uri) tuples.

chevron-rightRunnable PoC (optional)hashtag

test/v2/platform/signature-collision.test.ts performs these steps end-to-end, deploying the factory, reusing a valid signature, and asserting that the forged metadata is stored for the new collection.

chevron-rightPoC Codehashtag

Recommendation

  • Replace abi.encodePacked with abi.encode wherever signature hashes include dynamic types in SignatureVerifier.

  • Bind signatures to all critical parameters intended to be controlled by the platform (payment token, prices, supply caps, transferability, etc.).

  • Add nonce and expiry fields and bind the verifying contract address to prevent replay or cross-contract reuse.

Was this helpful?