First Flight #12: Kitty Connect

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

Incorrect access control in `KittyBridge::_ccipReceive` allows anyone to get free kitties

Summary

KittyBridge::_ccipReceive has the purpose of gatekeeping cross-chain messages by validating the sender and the chain id of the source chain. The problem in this process is who validates the function as the sender, instead of validating the originator the message, the function validates the caller of KittyBridge::ccipReceive which is the router of the destination chain - this means that as long the message comes from a valid source chain and the msg.sender is the router, that message will be accepted. This will allow anyone to create a message to instruct the bridge to mint a kitty for free.

function _ccipReceive(Client.Any2EVMMessage memory any2EvmMessage)
internal
override
@> onlyAllowlisted(any2EvmMessage.sourceChainSelector, msg.sender)
{
// rest of the function...
}

Vulnerability Details

The flow of the exploit can be explained in the following steps:

  1. Attacker deploys a contract with the ability to send cross-chain messages on an allowlisted source chain

  2. Attacker sends a transaction to instructo the contract to send a cross-chain message to a bridge on a destination chain

  3. The bridge on the destination chain receives the message and it is accepted because the msg.sender is the router and the source chain is allowedlisted

  4. The bridge calls KittyConnect::mintBridgedNFT with the data of the cross-chain message that contains the metadata of the NFT to mint to the specified owner

Proof of Concept

To prove the attack I've deployed the protocol on Polygon Mumbai and Ethereum Sepolia. Also I've deployed an Attacker contract at this address on Polygon Mumbai for faster testing. The attacker contract is already funded with LINK to pay for the fees.

# addresses
MUMBAI_ATTACKER = 0x284FB07c3c51629Bc48479d85AFA0BD123c8C5B8
SEPOLIA_KITTY_BRIDGE = 0xFd829a345fd28a0f6eeD6851822ebbCe6b923702
SEPOLIA_KITTY_CONNECT = 0xC55e8Fd249dAb84E563249340dA769FE8B7378cF

Follow the next steps:

  1. Call KittyBridgeAttacker::computePayloadon MUMBAI_ATTACKER with the address of the account to be owner of the kitty to mint on the destination chain

  2. Call KittyBridgeAttacker::sendMessagePayLINK with the chain id of Ethereum Sepolia, the receiver of the message (SEPOLIA_KITTY_BRIDGE) and the computed payload. The chain id of sepolia and address of the receiver are 16015286601757825753 and 0xFd829a345fd28a0f6eeD6851822ebbCe6b923702, respectively

  3. Use the transaction hash to find the transaction on CCIP Explorer and wait for finality

  4. After reaching block finality, verify the specified owner got a free NFT by callingKittyConnect::ownerOf, I recommend testing from three onward as token id.

Impact

Anyone can get free kitties for free, breaking the intended behaviour of the protocol.

Tools Used

Manual review

Recommendations

To properly gatekeep messages I recommend validating the originator of the message instead of the msg.sender.

function _ccipReceive(Client.Any2EVMMessage memory any2EvmMessage)
internal
override
+ onlyAllowlisted(any2EvmMessage.sourceChainSelector, abi.decode(any2EvmMessage.sender, (address)))
- onlyAllowlisted(any2EvmMessage.sourceChainSelector, msg.sender)
{
// rest of the function..
}
Updates

Lead Judging Commences

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

`onlyAllowlisted` modifier is not properly implemented in case of _ccipReceive

Support

FAQs

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