MorpheusAI

MorpheusAI
Foundry
22,500 USDC
View results
Submission Details
Severity: medium
Valid

Due to no access control on `L2MessageReceiverV2::_authorizeUpgrade()` anyone can change the implementation contract and can destroy the main Proxy contract.

Note: I have submitted total four reports of similar kind of this report. Although their issues are of same type but their root cause were different and fixing one will not fix another. So I treated them as 4 separate issues with their recommended mitigation

Vulnerability Details :

L2MessageReceiverV2 contract inheriting UUPSUpgradeable so overriding _authorizeUpgrade() function. But adding no access control to this function can lead to setting implementation contract by anyone since this function is called for authorization. By setting any malicious contract as implementation attacker can selfdestruct() the proxy contract. Since implementation is called using delegatecall from proxy.

Code Snippet :

contracts/mock/L2MessageReceiverV2.sol

11: function _authorizeUpgrade(address) internal view override {}

Since L2MessageReceiverV2 contract is inheriting UUPSUpgradeable from openzeppelin. We can see when upgrading implementation address using upgradeToAndCall then _authorizeUpgrade() is called.

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol#L86C5-L90C1

function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data);
}

Malicious L2MessageReceiverV2 implementation contract

contract L2MessageReceiverV2 is UUPSUpgradeable {
function version() external returns (uint256) {
return 2;
}
function destroyMe() public {
selfdestruct(address(0));
}
function _authorizeUpgrade(address) internal view override {}
}

First attacker will call upgradeToAndCall to main proxy contract which will change the implementation contract address to his malicious L2MessageReceiverV2 contract.
Now Attacker will call destroyMe to main proxy contract of L2MessageReceiverV2 and by delegatecall proxy will call implementation contract .It will destroy the main proxy contract using selfdestruct().

Impact :

The main proxy contract will be destroyed.

Tools Used

Manual Review

Recommended Mitigation Steps :

Add proper access control to _authorizeUpgrade() function since it is used for authorization at the time of changing implementation contract address. This can be mitigated by using 'OwnableUpgradeable' of openzeppelin and add onlyOwner modifier to the _authorizeUpgrade() function so. Only owner can be authorized to upgrade the implementation address. Add one initializer function also to initialize the owner.

File : contracts/mock/L2MessageReceiverV2.sol
...
+ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
- contract L2MessageReceiverV2 is UUPSUpgradeable {
+ contract L2MessageReceiverV2 is OwnableUpgradeable, UUPSUpgradeable {
...
+ function Distribution_init() external initializer
+ {
+ __Ownable_init();
+ __UUPSUpgradeable_init();
+ }
...
- function _authorizeUpgrade(address) internal view override {}
+ function _authorizeUpgrade(address) internal view override onlyOwner {}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

_authorizeUpgrade(address) lacks access control in mock contracts

Support

FAQs

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