NFTBridge
60,000 USDC
View results
Submission Details
Severity: low
Invalid

L1 bridge can be taken over by an attacker if `upgradeTo()` is used to upgrade the contract's implementation

Summary

L1 bridge can be taken over by an attacker if upgradeTo() is used to upgrade the contract's implementation. This is because of how initialize() is implemented in Bridge.sol

Vulnerability Details

The initialize() function in Bridge.sol is as follows :

function initialize(
bytes calldata data
)
public
onlyInit
{
(
address owner,
IStarknetMessaging starknetCoreAddress,
uint256 starklaneL2Address,
uint256 starklaneL2Selector
) = abi.decode(
data,
(address, IStarknetMessaging, uint256, uint256)
);
_enabled = false;
_starknetCoreAddress = starknetCoreAddress;
_transferOwnership(owner);
setStarklaneL2Address(starklaneL2Address);
setStarklaneL2Selector(starklaneL2Selector);
}

It has the onlyInit modifier :

modifier onlyInit() {
address impl = _getImplementation();
require(!_initializedImpls[impl], "Already init");
_initializedImpls[impl] = true;
_;
}

The problem with this logic is that the initialize() function needs to be called again when the contract is upgraded to a new implementation. Not only is this unnecessary but it also opens the contract to the possibility of the bridge being taken over by an attacker if the contract is upgraded using the upgradeTo() function of UUPSUpgradeable.sol

When the contract is upgraded by the owner using the upgradeTo() function, the contract can be initialized again since _initializedImpls[impl] will return false in the onlyInit modifier. Thus, an attacker can back-run the owners upgradeTo() transaction or front-run the owner's initialize() transaction to call initialize(), thus setting theirself as the owner of the contract and therefore taking over any assets that the bridge posseses.

Impact

Permanent loss of assets in given scenario. However, the owner can use upgradeToAndCall() instead to mitigate the possibility of this attack. Therefore, the attack requires the bridge owner to be unaware of this little detail.

Since the impact is High and the likelihood of the owner lacking knowledge of this vulnerability is Low - Medium, I believe Medium severity would be appropriate for this issue.

Tools Used

Manual Review

Recommendations

Calling the initializer every time the contract is upgraded not only makes the contract vulnerable to this attack, but also is completely uneccessary since the owner can independently alter all of the variables that are set in this function anyways.

Instead, you can replace the _initializedImpls[] mapping with the simple bool initialized variable to only allow the function to be called once.

Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

invalid-bridge-initialize-frontrun

If frontrun at the first deployment, protocol will deploy again, no real impact: informational. Moreover it is already deployed and initialize on mainnet. For the upgrades, `initialize` can/will change for the next update since the owner is already set. A lot of protocol make that change. That’s why I consider it like a future feature and it is out of scope.

Support

FAQs

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