56860 sc medium hash collision in signature verification

Submitted on Oct 21st 2025 at 10:18:50 UTC by @ciphermalware for Audit Comp | Belong

  • Report ID: #56860

  • Report Type: Smart Contract

  • Report severity: Medium

  • Target: https://github.com/belongnet/checkin-contracts/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

In SignatureVerifier.sol the use of abi.encodePacked to calculate hashes for several sequential strings (name, symbol and URI) used for signature verification results in hash collisions. An attacker can obtain a valid backend signature for one set of inputs and then reuse it for a colliding set. This allows any user to pass verification for metadata the signer did not approve.

Vulnerability Details

In checkAccessTokenInfo the hash is computed as:

keccak256(
    abi.encodePacked(
        accessTokenInfo.metadata.name,  
        accessTokenInfo.metadata.symbol,  
        accessTokenInfo.contractURI,  
        accessTokenInfo.feeNumerator, 
        block.chainid  
    )
)

Three strings are concatenated one after another before the fixed uints. Two different payloads can produce the same concatenated bytes, causing identical keccak256 hashes. Once the backend signs Payload A, a user can present Payload B (that collides) with the same signature and pass verification because signer.isValidSignatureNow will return true.

The difference between name and symbol is ambiguous when using packed encoding, so characters can be moved between fields without changing the packed bytes. This is the exact collision scenario warned against in the Solidity docs.

Recommended remediation: replace abi.encodePacked() with abi.encode() in all signature verification functions. As noted in the Solidity docs: "Unless there is a compelling reason, abi.encode should be preferred."

Impact Details

This is an authorization bypass: an attacker can alter metadata the signer intended to restrict. No on-chain privileges are required beyond possessing a valid signature for some triplet—the attack manipulates field boundaries while preserving bytes, so verification succeeds.

References

  • Solidity ABI spec (Non-standard Packed Mode warning): https://docs.soliditylang.org/en/latest/abi-spec.html

Proof of Concept

The author used Foundry. Files added: SignatureVerifier.sol and Structures.sol in src, and SignatureVerifier.t.sol in test.

Test contract SignatureVerifier.t.sol:

Test command:

Observed output confirming collisions and signature reuse:

circle-info

Suggested fix (as stated in the report): use abi.encode(...) instead of abi.encodePacked(...) when constructing the message hash for signature verification. This prevents dynamic fields from being concatenated ambiguously and avoids the described collision class.

Was this helpful?