SCWE-098: Initialization Front-Running in Upgradeable Contracts
Stable Version v1.0
This content is in the version-(v1.0) and still under active development, so it is subject to change any time (e.g. structure, IDs, content, URLs, etc.).
Relationships¶
- CWE-284: Improper Access Control
https://cwe.mitre.org/data/definitions/284.html
Description¶
Proxy-based contracts ship without constructors; if the initializer is callable by anyone, an attacker can front-run deployment and seize ownership, set trusted roles, or lock upgrades. This permanently compromises the implementation behind the proxy.
Remediation¶
- Protect initializer functions with access control and
initializer/reinitializerguards. - Invoke initialization atomically during deployment or via a deploy-time call data payload.
- Use
disableInitializers()on the implementation contract to prevent accidental takeover.
Examples¶
Vulnerable¶
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract UpgradeableVault is Initializable {
address public owner;
function initialize(address _owner) public initializer {
owner = _owner; // anyone can front-run and become owner
}
}
Fixed¶
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract UpgradeableVault is Initializable {
address public owner;
function initialize(address _owner, address deployer) public initializer {
require(msg.sender == deployer, "only deployer");
owner = _owner;
}
}
initialize(owner, address(this)) in the same transaction — no window for front-running.