The sendToL1() function is marked public. As such the following attack path is possible:
Attacker deposits 1 wei (or 0 wei) into the L2 bridge.
Attacker crafts and encodes a malicious message and submits it to the operator to be signed by him. The malicious message has target field equal to vault and the data field equal to a call to approve() to attain max token approval by the vault for the attacker. (This malicious message could be anything really, but choosing this example to demonstrate a not-so-simple attack).
Since the attacker had deposited 1 wei, operator approves & signs the message, not knowing the contents of it since it is encoded.
Attacker directly calls sendToL1() instead of going via withdrawTokensToL1().
Line 121 executes the low-level call and since from the vault's perspective, it originates from the L1BossBridge.sol contract as the msg.sender, it executes successfully validating the onlyOwner modifier.
Attacker now (or sometime in the future) calls transferFrom() to clean up the vault completely.
The following PoC shows the above attack vector by draining all the funds from the vault.
Paste the following test inside test/L1TokenBridge.t.sol
Run with forge test --mt test_t0x1cVaultCleaner -vv
Attacker can steal all the funds from the vault.
Foundry
Mark the sendToL1() function as internal instead of public so that user is forced to go via withdrawTokensToL1().The withdrawTokensToL1() is susceptible to maliciously crafted message too, but that is addressed in a different bug report titled:
By sending any
amount, attacker can withdraw more funds than deposited
The combination of the two fixes will secure the protocol against such attack vectors.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.