Due to no access control on `WellUpgradeable::_authorizeUpgrade()` anyone can change the implementation contract and can destroy the main Proxy contract. #101
WellUpgradeable 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 authorization. By setting any malicious contract as implementation attacker can selfdestruct() the proxy contract. Since implementation is called using delegatecall from proxy.
65: function _authorizeUpgrade(address newImplmentation) internal view override {
// verify the function is called through a delegatecall.
require(address(this) != ___self, "Function must be called through delegatecall");
// verify the function is called through an active proxy bored by an aquifer.
address aquifer = aquifer();
address activeProxy = IAquifer(aquifer).wellImplementation(_getImplementation());
require(activeProxy == ___self, "Function must be called through active proxy bored by an aquifer");
// verify the new implmentation is a well bored by an aquifier.
require(
IAquifer(aquifer).wellImplementation(newImplmentation) != address(0),
"New implementation must be a well implmentation"
);
// verify the new implmentation is a valid ERC-1967 implmentation.
require(
UUPSUpgradeable(newImplmentation).proxiableUUID() == _IMPLEMENTATION_SLOT,
"New implementation must be a valid ERC-1967 implmentation"
);
Since WellUpgradeable contract is inheriting UUPSUpgradeable from openzeppelin and WellUpgradeable overrides upgradeTo ,we can see in upgrading implementation address _authorizeUpgrade() is called.
function upgradeTo(address newImplementation) public override {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, new bytes(0), false);
}
Malicious WellUpgradeable implementation contract
contract WellUpgradeable is UUPSUpgradeable {
function destroy() public override {
selfdestruct(address(0));//@audit this code will be implanted here
}
function _authorizeUpgrade(address) internal view override {}
}
When destroy() will be called from proxy contract using delegatecall it will destroy the main proxy contract using selfdestruct(). And erase all the storage from main proxy contract of WellUpgradeable.
Impact :
The main proxy contract will be destroyed and erase all the storage from main proxy contract of WellUpgradeable.
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. Add onlyOwner modifier to the _authorizeUpgrade() function so. Only owner can be authorized to upgrade the implementation address.
Lines of code
https://github.com/code-423n4/2024-07-basin/blob/main/src/WellUpgradeable.sol#L65-L84
Vulnerability details
Vulnerability Details :
WellUpgradeable
contract inheritingUUPSUpgradeable
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 authorization. By setting any malicious contract as implementation attacker can selfdestruct() the proxy contract. Since implementation is called using delegatecall from proxy.Code Snippet :
src/WellUpgradeable.sol#L65-L84
Since
WellUpgradeable
contract is inheritingUUPSUpgradeable
from openzeppelin andWellUpgradeable
overridesupgradeTo
,we can see in upgrading implementation address_authorizeUpgrade()
is called.https://github.com/code-423n4/2024-07-basin/blob/main/src/WellUpgradeable.sol#L93C5-L96C6
Malicious
WellUpgradeable
implementation contractWhen
destroy()
will be called from proxy contract usingdelegatecall
it will destroy the main proxy contract usingselfdestruct()
. And erase all the storage from main proxy contract of WellUpgradeable.Impact :
The main proxy contract will be destroyed and erase all the storage from main proxy contract of WellUpgradeable.
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. AddonlyOwner
modifier to the_authorizeUpgrade()
function so. Only owner can be authorized to upgrade the implementation address.Assessed type
Access Control