Beginner FriendlyFoundryBridge
100 EXP
View results
Submission Details
Severity: high
Valid

Signature Replay Attack on L1BossBridge::sendToL1 function, resulting in unauthorized transfers of funds

Summary

The sendToL1 function is vulnerable to Signature Replay Attack, since there is no check to see if the signature was used before or not.

Vulnerability Details

A Signature Replay Attack is a type of security vulnerability that occurs when an attacker captures a valid cryptographic signature and attempts to use it again to execute unauthorized actions. This can lead to unexpected behaviors in smart contracts, allowing the attacker to perform actions they are not authorized for.

Impact

In the provided sendToL1 function, the contract uses the ECDSA.recover function to recover the signer's address from the signature. However, the contract does not check if the signature has been used before. This means that an attacker could capture a validly signed transaction and replay it multiple times, resulting in unauthorized transfers of funds.

Tools Used

Manual Analysis and AI tool phind.

Recommendations

To prevent Signature Replay Attacks, you could incorporate a nonce and timestamp mechanism into your contract logic. A nonce is a unique identifier for each transaction, ensuring that each transaction can only be executed once. Additionally, using timestamps can add an extra layer of security by allowing contracts to reject transactions that are too old to prevent replay attempts.

Here's an example of how you could modify the sendToL1 function to include a nonce and timestamp:

+ mapping(uint256 => bool) public usedNonces;
- function sendToL1(uint8 v, bytes32 r, bytes32 s, bytes memory message) public nonReentrant whenNotPaused {
+ function sendToL1(uint8 v, bytes32 r, bytes32 s, bytes memory message, uint256 nonce, uint256 timestamp) public nonReentrant whenNotPaused {
// Check if the nonce has been used before
+ require(!usedNonces[nonce], "Nonce has already been used");
// Check if the transaction is too old
+ require(block.timestamp <= timestamp + 15 minutes, "Transaction is too old");
- address signer = ECDSA.recover(MessageHashUtils.toEthSignedMessageHash(keccak256(message)), v, r, s);
+ address signer = ECDSA.recover(MessageHashUtils.toEthSignedMessageHash(keccak256(abi.encodePacked(message, nonce, timestamp))), v, r, s);
if (!signers[signer]) {
revert L1BossBridge__Unauthorized();
}
(address target, uint256 value, bytes memory data) = abi.decode(message, (address, uint256, bytes));
(bool success,) = target.call{ value: value }(data);
if (!success) {
revert L1BossBridge__CallFailed();
}
// Mark the nonce as used
+ usedNonces[nonce] = true;
}

Now in the updated code, the contract checks if the nonce has been used before and if the transaction is too old before it recovers the signer's address. After the transaction is executed, the contract marks the nonce as used. This prevents the same signature from being replayed.

Updates

Lead Judging Commences

0xnevi Lead Judge almost 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

withdrawTokensToL1()/sendToL1(): signature replay

Support

FAQs

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