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

Potential Front-Running attack, Attacker could withdraw tokens from L2 before the user

Summary

A signed transaction can be exploited by an attacker to front-run the withdrawal of tokens from L2 to L1, allowing the attacker to gain control of the tokens before the intended recipient. This vulnerability could potentially result in unauthorized token transfers if not properly mitigated.

Vulnerability Details

The vulnerability allows an attacker to front-run token withdrawals from L2 to L1, potentially gaining control of tokens intended for another recipient.

Impact

The impact is that an attacker can manipulate token withdrawals, potentially stealing tokens intended for a legitimate recipient.

Tools Used

  • Manual review

Recommendations

Mitigating the issue primarily involves addressing replay attacks. To prevent replay attacks in the provided code, you can introduce a nonce and use Ethereum's EIP-712 standard for signing messages. Here's what you should do:

// Define a mapping to store nonces for each signer
mapping(address => uint256) public nonces;
function withdrawTokensToL1(address to, uint256 amount, uint8 v, bytes32 r, bytes32 s) external {
// Create a unique nonce for this transaction
uint256 nonce = nonces[msg.sender]++;
// Construct the message to be signed using EIP-712 format
bytes32 messageHash = keccak256(
abi.encodePacked(
"\x19\x01",
getDomainSeparator(),
keccak256(abi.encodePacked(
address(this),
to,
amount,
nonce
))
)
);
address signer = ECDSA.recover(messageHash, v, r, s);
// Check if the recovered signer is valid
require(signers[signer], "Invalid signer");
// Ensure the nonce is valid and not already used
require(nonce == nonces[signer], "Invalid nonce");
// Proceed with the token withdrawal logic
token.transferFrom(vault, to, amount);
}
// Define the EIP-712 domain separator
function getDomainSeparator() public view returns (bytes32) {
return keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("YourContractName")), // Replace with your contract name
keccak256(bytes("1")), // Replace with your contract version
chainId(),
address(this)
)
);
}
// Define a function to get the current chain ID
function chainId() internal pure returns (uint256) {
uint256 id;
assembly {
id := chainid()
}
return id;
}
Updates

Lead Judging Commences

0xnevi Lead Judge over 1 year 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.