56869 sc medium hijacking deployment of accesstoken and stealing ownership to prevent further deployments

Submitted on Oct 21st 2025 at 11:39:54 UTC by @blackgrease for Audit Comp | Belongarrow-up-right

  • Report ID: #56869

  • Report Type: Smart Contract

  • Report severity: Medium

  • Target: https://github.com/belongnet/checkin-contracts/blob/main/contracts/v2/platform/Factory.sol

Impacts

  • Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield

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

  • Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)

Description

Affected Files: Factory.sol and AccessToken.sol

The Factory::produce function creates a new AccessToken collection and an optional RoyaltiesReceiver (if feeNumerator > 0). The function requires a signature from an authorized signer on a hash of NFT name, symbol, contractURI, feeNumerator and block.chainid. However, the function is permissionless — anyone can call it with a valid signature. The deployment uses CREATE2 deterministic addresses and AccessToken is Ownable, so the caller becomes the owner of the deployed instance.

The salt hash is derived from token name and symbol; for a given name/symbol pair there cannot be duplicate AccessToken instances:

//--snip--
bytes32 hashedSalt = _metadataHash(accessTokenInfo.metadata.name, accessTokenInfo.metadata.symbol);
require(getNftInstanceInfo[hashedSalt].nftAddress == address(0), TokenAlreadyExists());
//--snip

The Issue

An attacker can front-run Factory::produce to take ownership of the AccessToken collection and gain full ownership of the instance. Because only one deployment exists for a given token name & symbol, the intended user's subsequent transaction will fail, causing a denial of service.

Factory::produce does not validate variables beyond those signed, allowing an attacker to alter values like feeNumerator. If feeNumerator > 0, this deploys a RoyaltiesReceiverV2 which the attacker can receive profits from.

By taking over the AccessToken and becoming owner, an attacker can:

  • prevent any other deployment of the same name/symbol pair (denial of service),

  • change minting prices,

  • receive royalties if feeNumerator was set,

  • upgrade AccessToken to a malicious version and alter the NFT's behavior/content.

The legitimate user would need the Signer to sign new metadata, but the attack can be repeated, so the issue persists until fixed.

Full problematic code (excerpt)

Impact

  • Disruption of legitimate operations: further AccessToken deployments for the same name/symbol will revert (DoS).

  • Loss of royalties for the intended recipient if an attacker set feeNumerator.

  • Business loss due to altered mint prices.

  • Griefing by making NFTs inaccessible or modifying mint economics.

  • Arbitrary upgrades of AccessToken to change NFT behavior/content.

Likelihood of exploitation: High (no execution restrictions; attacker simply front-runs a transaction).

Mitigation

Control who can call Factory::produce ensuring the caller matches the intended input, or include the caller in the salt so the salt is unique per caller:

Suggested change:

This prevents an attacker from successfully front-running another caller for the same name/symbol.

https://gist.github.com/blackgrease/46e26dc7097dc866d2311ce7711f4523

Proof of Concept

The PoC (in the gist) demonstrates:

  1. Front-running an intended owner's transaction to claim AccessToken ownership.

  2. Confirming the attacker is owner by executing onlyOwner functionality.

  3. Confirming that only one AccessToken exists per name/symbol pair by showing the legitimate user's deployment reverts.

Conversion to Foundry and running the PoC

1

Setup

  1. Clone the GitHub repo:

    • git clone https://github.com/belongnet/checkin-contracts.git

2

Install Foundry dependencies

  1. forge install OpenZeppelin/[email protected] --no-commit

  2. forge install OpenZeppelin/[email protected] --no-commit

  3. npm install solady --force

3

Update remappings in foundry.toml

Replace or update remappings as shown:

4

Run the PoC

  • Run with: forge test --mt testAddressHijackingAndAccessTokenOwnershipTakeoever -vvv

Note: In case of issues running the PoC, the gist includes a Foundry stack trace file: "PoC_StackTrace_AddressHijackingAndAccessTokenOwnershipTakeoever.txt"


End of report.

Was this helpful?