First Flight #12: Kitty Connect

First Flight #12: Kitty Connect
Beginner FriendlyFoundryNFTGameFi
100 EXP
View results
Submission Details
Severity: high
Valid

Anyone can call `KittyBridge::bridgeNftWithData()` to mint a NFT on another chain

Summary

Anyone can call KittyBridge::bridgeNftWithData() allowing them to mint a NFT on a different chain without actually owning a NFT provided by a shop partner.

Vulnerability Details

The KittyBridge::bridgeNftWithData() function is responsible for building and sending the CCIP message:

function bridgeNftWithData(uint64 _destinationChainSelector, address _receiver, bytes memory _data)
external
onlyAllowlistedDestinationChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(_receiver, _data, address(s_linkToken));
// Initialize a router client instance to interact with cross-chain router
IRouterClient router = IRouterClient(this.getRouter());
// Get the fee required to send the CCIP message
uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage);
if (fees > s_linkToken.balanceOf(address(this))) {
revert KittyBridge__NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
}
messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage);
emit MessageSent(messageId, _destinationChainSelector, _receiver, _data, address(s_linkToken), fees);
return messageId;
}

The ideal scenario would be that this function gets called from the KittyConnect::bridgeNftToAnotherChain() function since the function verifies the owner of the NFT, and burns the NFT, before finally calling the KittyBridge::bridgeNftWithData() with the appropriate data of the cat NFT.

However since KittyBridge::bridgeNftWithData() is external and only checks for onlyAllowlistedDestinationChain, and validateReceiver, any contract or account can call this function passing in any arbitrary data to represent a cat NFT that isn't provided by a shop partner.

Impact

  • Allows a user to mint their own version of a cat NFT on a different chain not provided by an official shop partner

Tools Used

VS Code, Foundry

Recommendations

Create a onlyKittyConnect modifier and add it to the KittyBridge::bridgeNftWithData() function:

+ modifier onlyKittyConnect() {
+ require(
+ msg.sender == kittyConnect,
+ "KittyConnect__NotKittyConnect"
+ );
+ _;
+ }
function bridgeNftWithData(uint64 _destinationChainSelector, address _receiver, bytes memory _data)
external
+ onlyKittyConnect()
onlyAllowlistedDestinationChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(_receiver, _data, address(s_linkToken));
// Initialize a router client instance to interact with cross-chain router
IRouterClient router = IRouterClient(this.getRouter());
// Get the fee required to send the CCIP message
uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage);
if (fees > s_linkToken.balanceOf(address(this))) {
revert KittyBridge__NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
}
messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage);
emit MessageSent(messageId, _destinationChainSelector, _receiver, _data, address(s_linkToken), fees);
return messageId;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

`bridgeNftWithData` misses access control

Support

FAQs

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