First Flight #12: Kitty Connect

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

KittyBridge:bridgeNftWithData(): Users are unable to bridge nft

Summary

Users are unable to bridge their nft to another chain from the KittyConnect through bridgeNftToAnotherChain() or directly on KittyBridge through bridgeNftWithData().

Vulnerability Details

When a user tries to bridge an nft, the user will get an error [FAIL. Reason: revert: SafeERC20: low-level call failed]
On further inspection, i discovered that the bridgeNftWithData() does not implement approval for the router so that the router will be able to send the fee required to send the CCIP message.

function bridgeNftWithData(uint64 _destinationChainSelector, address _receiver, bytes memory _data)
external
onlyAllowlistedDestinationChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
// @audit attacker can mint nfts
// 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);
}
// @audit contract needs to approve router to spend link token on its behalf
messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage);
emit MessageSent(messageId, _destinationChainSelector, _receiver, _data, address(s_linkToken), fees);
return messageId;
}

Impact

  1. All users are unable to bridge their nfts to other chains

POC

function test_bridgeNft() public {
address sender = makeAddr("sender");
uint64 chainId = 16015286601757825753;
bytes memory data = abi.encode(
makeAddr("catOwner"),
"meowdy",
"ragdoll",
"ipfs://QmbxwGgBGrNdXPm84kqYskmcMT3jrzBN8LzQjixvkz4c62",
block.timestamp,
partnerA
);
vm.prank(address(kittyConnectOwner));
kittyBridge.allowlistDestinationChain(chainId, true);
vm.prank(sender);
vm.expectRevert();
kittyBridge.bridgeNftWithData(chainId, sender, data);
}

Tools Used

VS Code, Foundry, Manual Review

Recommendations

Add the approval of link tokens for the router to the bridgeNftWithData()

function bridgeNftWithData(uint64 _destinationChainSelector, address _receiver, bytes memory _data)
external
onlyAllowlistedDestinationChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
// @audit attacker can mint nfts
// 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);
}
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK
+ s_linkToken.approve(address(router), 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:

Missing fee token approval

Support

FAQs

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