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

Replay Attack Vulnerability in Starklane Smart Contract Due to Unstored Request Hashes

Summary

This report identifies a replay attack vulnerability in the Starklane smart contract. The vulnerability exists due to the absence of a mechanism to store and track request hashes, allowing malicious actors to reuse the same request data to replay transactions. This can lead to unauthorized token transfers and potential financial losses.

Vulnerability Detail

The vulnerability is located in the depositTokens function of the Starklane contract. Specifically, the function generates a unique hash for each token deposit request but does not store this hash in storage. Consequently, the same request can be replayed, leading to multiple unauthorized token transfers.

Code Snippet

solidity
function depositTokens(
uint256 salt,
address collectionL1,
snaddress ownerL2,
uint256[] calldata ids,
bool useAutoBurn
)
external
payable
{
if (!Cairo.isFelt252(snaddress.unwrap(ownerL2))) {
revert CairoWrapError();
}
if (!_enabled) {
revert BridgeNotEnabledError();
}

CollectionType ctype = TokenUtil.detectInterface(collectionL1);
if (ctype == CollectionType.ERC1155) {
revert NotSupportedYetError();
}
if (!_isWhiteListed(collectionL1)) {
revert NotWhiteListedError();
}
Request memory req;
req.hash = Protocol.requestHash(salt, collectionL1, ownerL2, ids);
// Check if the request has already been processed
if (processedRequests[req.hash]) {
revert("Request has already been processed");
}
// Mark the request as processed
processedRequests[req.hash] = true;
req.header = Protocol.requestHeaderV1(ctype, useAutoBurn, false);
req.collectionL1 = collectionL1;
req.collectionL2 = _l1ToL2Addresses[collectionL1];
req.ownerL1 = msg.sender;
req.ownerL2 = ownerL2;
if (ctype == CollectionType.ERC721) {
(req.name, req.symbol, req.uri, req.tokenURIs) = TokenUtil.erc721Metadata(collectionL1, ids);
} else {
(req.uri) = TokenUtil.erc1155Metadata(collectionL1);
}
_depositIntoEscrow(ctype, collectionL1, ids);
req.tokenIds = ids;
uint256[] memory payload = Protocol.requestSerialize(req);
if (payload.length >= MAX_PAYLOAD_LENGTH) {
revert TooManyTokensError();
}
IStarknetMessaging(_starknetCoreAddress).sendMessageToL2{value: msg.value}(
snaddress.unwrap(_starklaneL2Address),
felt252.unwrap(_starklaneL2Selector),
payload
);
emit DepositRequestInitiated(req.hash, block.timestamp, payload);

}

Impact

The replay attack vulnerability can have severe consequences, including:

  1. Unauthorized Token Transfers:

    Attackers can replay the same request to transfer tokens multiple times without authorization.

  2. Financial Loss:

    Repeated unauthorized transactions can lead to significant financial losses for users.

  3. Trust Issues:

    Users may lose trust in the platform due to the perceived insecurity, affecting the overall reputation of the project.

Recommendations

To mitigate this vulnerability, the contract should store the request hashes in storage and ensure each request is processed only once. Here are the steps to implement this:

  1. Add a Mapping for Storing Request Hashes*: Create a mapping to store processed request hashes.

solidity
mapping(bytes32 => bool) private processedRequests;

  1. Check and Store Request Hashes*: Update the depositTokens function to check if the request hash has already been processed and store it.

solidity
function depositTokens(
uint256 salt,
address collectionL1,
snaddress ownerL2,
uint256[] calldata ids,
bool useAutoBurn
)
external
payable
{
if (!Cairo.isFelt252(snaddress.unwrap(ownerL2))) {
revert CairoWrapError();
}
if (!_enabled) {
revert BridgeNotEnabledError();
}

CollectionType ctype = TokenUtil.detectInterface(collectionL1);
if (ctype == CollectionType.ERC1155) {
revert NotSupportedYetError();
}
if (!_isWhiteListed(collectionL1)) {
revert NotWhiteListedError();
}
Request memory req;
req.hash = Protocol.requestHash(salt, collectionL1, ownerL2, ids);
// Check if the request has already been processed
if (processedRequests[req.hash]) {
revert("Request has already been processed");
}
// Mark the request as processed
processedRequests[req.hash] = true;
req.header = Protocol.requestHeaderV1(ctype, useAutoBurn, false);
req.collectionL1 = collectionL1;
req.collectionL2 = _l1ToL2Addresses[collectionL1];
req.ownerL1 = msg.sender;
req.ownerL2 = ownerL2;
if (ctype == CollectionType.ERC721) {
(req.name, req.symbol, req.uri, req.tokenURIs) = TokenUtil.erc721Metadata(collectionL1, ids);
} else {
(req.uri) = TokenUtil.erc1155Metadata(collectionL1);
}
_depositIntoEscrow(ctype, collectionL1, ids);
req.tokenIds = ids;
uint256[] memory payload = Protocol.requestSerialize(req);
if (payload.length >= MAX_PAYLOAD_LENGTH) {
revert TooManyTokensError();
}
IStarknetMessaging(_starknetCoreAddress).sendMessageToL2{value: msg.value}(
snaddress.unwrap(_starklaneL2Address),
felt252.unwrap(_starklaneL2Selector),
payload
);
emit DepositRequestInitiated(req.hash, block.timestamp, payload);

}

By implementing these changes, the replay attack vulnerability can be effectively mitigated, ensuring the security and integrity of the Starklane smart contract.

Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

invalid-replay-attack-hash-not-stored-nonce-not-used

There is no impact here: Transaction cannot be replayed because the blockchain use the nonce in the signature. Hash is computed on-chain. Using or trying to have the same hash mean you need to buy the token, and they will be sent to their origin owner. Why an attacker would buy tokens to give them back ? No real impact.

Support

FAQs

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