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.
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:
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.