Summary
A signature has no expiration in Boss Bridge. Having a signature expiration limits the ability for signature replay attacks.
Vulnerability Details
There is no limit on how long until a signature is used in withdrawTokensToL1 or sendToL1:
function withdrawTokensToL1(
address to,
uint256 amount,
uint8 v,
bytes32 r,
bytes32 s
) external {
sendToL1(
v,
r,
s,
abi.encode(
address(token),
0,
abi.encodeCall(
IERC20.transferFrom,
(address(vault), to, amount)
)
)
);
}
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 (!signers[signer]) {
revert L1BossBridge__Unauthorized();
}
(address target, uint256 value, bytes memory data) = abi.decode(
message,
(address, uint256, bytes)
);
(bool success, ) = target.call{value: value}(data);
if (!success) {
revert L1BossBridge__CallFailed();
}
}
}
Impact
The lack of expiration of signatures puts this bridge at greater risk of signature reuse.
Tools Used
Manual review
Recommendations
Modify the withdrawTokensToL1 function so that the message includes a timestamp:
function withdrawTokensToL1(
address to,
uint256 amount,
uint8 v,
bytes32 r,
bytes32 s
) external {
uint256 timestamp = block.timestamp;
sendToL1(
v,
r,
s,
abi.encode(
address(token),
0,
timestamp,
abi.encodeCall(
IERC20.transferFrom,
(address(vault), to, amount)
)
)
);
}