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

Replay Attack Risk in Starklane Messaging Contract

Summary

The Starklane Messaging contract contains a critical vulnerability that exposes it to replay attacks, which could compromise the security and integrity of the entire messaging system.

This stems from the absence of message expiration and reuse prevention mechanisms, allowing malicious actors to potentially exploit the system through replay attacks.

Vulnerability Details

The Starklane Messaging contract relies heavily on the IStarknetMessaging interface to facilitate communication between Layer 1 (Ethereum) and Layer 2 (Starknet).

However, the current implementation lacks essential security measures to prevent message replay attacks, which could lead to severe consequences.

Key aspects :

  1. Lack of Nonce Tracking: The contract does not implement any mechanism to track unique messages, making it vulnerable to message reuse .

  2. Absence of Expiration Checks: There are no built-in expiration mechanisms for messages, allowing old messages to potentially be processed repeatedly [2][4].

  3. Potential for Unauthorized Actions: Without proper safeguards, malicious actors could exploit the system by reusing previously consumed messages, leading to unauthorized actions or transfers .

  4. Double Spending Risk: The lack of uniqueness tracking increases the risk of double spending in token transfer scenarios .

  5. System Integrity Compromise: The integrity of the messaging system itself could be compromised, affecting overall trust and functionality of the contract .

Affected Code Blocks

function addMessageHashForAutoWithdraw(
uint256 msgHash
)
external
payable
onlyOwner
{
bytes32 hash = bytes32(msgHash);
if (_autoWithdrawn[hash] != WITHDRAW_AUTO_NONE) {
revert WithdrawMethodError();
}
_autoWithdrawn[hash] = WITHDRAW_AUTO_READY;
emit MessageHashAutoWithdrawAdded(hash);
}
function _consumeMessageAutoWithdraw(
snaddress fromL2Address,
uint256[] memory request
)
internal
{
bytes32 msgHash = keccak256(
abi.encodePacked(
snaddress.unwrap(fromL2Address),
uint256(uint160(address(this))),
request.length,
request)
);
uint256 status = _autoWithdrawn[msgHash];
if (status == WITHDRAW_AUTO_CONSUMED) {
revert WithdrawAlreadyError();
}
_autoWithdrawn[msgHash] = WITHDRAW_AUTO_CONSUMED;
}
function _consumeMessageStarknet(
IStarknetMessaging starknetCore,
snaddress fromL2Address,
uint256[] memory request
)
internal
{
bytes32 msgHash = starknetCore.consumeMessageFromL2(
snaddress.unwrap(fromL2Address),
request
);
if (_autoWithdrawn[msgHash] != WITHDRAW_AUTO_NONE) {
revert WithdrawMethodError();
}
}

Impact

  1. Unauthorized Actions: Malicious actors could potentially perform unauthorized withdrawals or transfers by reusing previously consumed messages .

  2. Double Spending: The same message could be used multiple times, leading to double-spending in token transfer scenarios .

  3. System Integrity Risk: The integrity of the messaging system could be compromised, resulting in financial loss and erosion of user trust .

  4. Long-term Vulnerability: This issue persists over time unless addressed, allowing continuous exploitation attempts .

Tools Used

Manual Code Review

Static Analysis Tools

Recommendations

To address this critical vulnerability, the following mitigations are necessary:

  1. Nonce Tracking: Implementing a nonce parameter ensures each message is unique and can only be processed once .

  2. Expiration Checks: Adding timestamp-based expiration logic prevents old messages from being consumed after a certain period.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "openzeppelin-contracts/contracts/access/Ownable.sol";
import "starknet/IStarknetMessaging.sol";
import "./sn/Cairo.sol";
uint256 constant WITHDRAW_AUTO_NONE = 0x00;
uint256 constant WITHDRAW_AUTO_READY = 0x01;
uint256 constant WITHDRAW_AUTO_CONSUMED = 0x02;
uint256 constant EXPIRATION_TIME = 1 days; // 🟢 Define message expiration time
error WithdrawMethodError();
error WithdrawAlreadyError();
contract StarklaneMessaging is Ownable {
mapping(bytes32 => uint256) _autoWithdrawn;
mapping(bytes32 => uint256) public messageNonces; // 🟢 Mapping for nonce tracking
event MessageHashAutoWithdrawAdded(bytes32 msgHash);
function addMessageHashForAutoWithdraw(
uint256 msgHash,
uint256 nonce // 🟢 Nonce added for uniqueness
)
external
payable
onlyOwner
{
bytes32 hash = keccak256(abi.encodePacked(msgHash, nonce)); // 🟢 Include nonce in hash
if (_autoWithdrawn[hash] != WITHDRAW_AUTO_NONE) {
revert WithdrawMethodError();
}
_autoWithdrawn[hash] = WITHDRAW_AUTO_READY;
messageNonces[hash] = block.timestamp; // 🟢 Store timestamp for expiration checks
emit MessageHashAutoWithdrawAdded(hash);
}
function _consumeMessageAutoWithdraw(
snaddress fromL2Address,
uint256[] memory request,
uint256 nonce // 🟢 Nonce for uniqueness
)
internal
{
bytes32 msgHash = keccak256(
abi.encodePacked(
snaddress.unwrap(fromL2Address),
uint256(uint160(address(this))),
request.length,
request,
nonce // 🟢 Include nonce in hash
)
);
uint256 status = _autoWithdrawn[msgHash];
require(block.timestamp - messageNonces[msgHash] < EXPIRATION_TIME, "Message expired"); // 🟢 Expiration check
if (status == WITHDRAW_AUTO_CONSUMED) {
revert WithdrawAlreadyError();
}
_autoWithdrawn[msgHash] = WITHDRAW_AUTO_CONSUMED;
}
function _consumeMessageStarknet(
IStarknetMessaging starknetCore,
snaddress fromL2Address,
uint256[] memory request,
uint256 nonce // 🟢 Nonce for uniqueness
)
internal
{
bytes32 msgHash = keccak256(
abi.encodePacked(
starknetCore.consumeMessageFromL2(
snaddress.unwrap(fromL2Address),
request
),
nonce // 🟢 Include nonce in hash
)
);
require(block.timestamp - messageNonces[msgHash] < EXPIRATION_TIME, "Message expired"); // 🟢 Expiration check
if (_autoWithdrawn[msgHash] != WITHDRAW_AUTO_NONE) {
revert WithdrawMethodError();
}
}
}
Updates

Lead Judging Commences

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

Support

FAQs

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