Full Report
This article goes into the security problems that can occur while using proxies. This website is meant to be all the research to do with proxies in the blockchain space. The first vulnerability mentioned is unintialized proxy. When updating the proxy to use a new implementation, there's a problem: the constructor will not be evaluated on the contract. Naturally, we need to initialize state when adding a new implementation. So, the implementation contract should have an initialize() function. The initialization step must happen separately from the deployment. So, there is a race condition where the function could be called. If an attacker called this, or the function was forgotten about, an attacker could cause major havoc. To test for the uninitialized contract, a few cases should be run: Is the contract initialized? Can it be reinitialzied? Is there a race condition between implementation deployment and initialization execution? Is there a access control on this function? Wormhole and OpenZeppelin are great examples of this. The second vulnerability is storage collision. When calling delegateCall the implementation contract is using the storage of the proxy contract. If there is a collision between these two contracts for variables, then havoc can ensure. In the case of Audis, proxyadmin was stored in the initializable field for the contract. This allowed the contract to be reinitialized and steal the funds. To test for this vulnerability, sol2uml can be used to visualize the storage slots from proxy to implementation. Additionally, using the artifacts between compilations of different versions would work too. This vulnerability can be particularly common with updates, since this could reorder the variables. Function clashing occurs when the 4 byte identifier of a function selector is the same. If a proxy function and an admin function have the same selector, this can cause problems. Slither is able to detect this problem automatically. The next two vulnerabilities are with using delegateCall. Redirecting to an arbitrary contract allows for the contract to alter internal variables. The next issue is figuring out a selfdestruct call from the initial call in the proxy. By doing this, the address and variables are ruined forever. The final issue is calls with delegateCall not checking the result. By not checking the result, the function would have executed without anything happening. delegateCall doesn't revert on not calling a contract; it only returns a boolean to mention this. Overall, a good read into proxy based vulnerabilities.
Analysis Summary
# Vulnerability: Smart Contract Proxy Pattern Security Flaws
## CVE Details
*Note: The provided text describes classes of vulnerabilities rather than specific CVE IDs. Below are relevant identifiers for the specific incidents mentioned (Audius and OpenZeppelin/Wormhole).*
- **CVE ID:** CVE-2022-35914 (OpenZeppelin Initializer), CVE-2022-29149 (Audius/OpenZeppelin Storage)
- **CVSS Score:** 9.8 (Critical)
- **CWE:** CWE-665 (Improper Initialization), CWE-710 (Improper Adherence to Coding Standards), CWE-822 (Untrusted Pointer Dereference)
## Affected Systems
- **Products:** Smart Contracts utilizing Proxy Patterns (EIP-1967, UUPS, Transparent Proxy).
- **Versions:** OpenZeppelin Contracts <4.4.1 (specifically `Initializable.sol`); Audius protocol.
- **Configurations:** Contracts where `delegatecall` is used to forward calls to implementation logic without proper initialization or storage layout validation.
## Vulnerability Description
This summary encompasses several critical flaws inherent in proxy-based architectures:
1. **Uninitialized Proxies:** Because logic contracts cannot use standard constructors, they rely on `initialize()` functions. A race condition exists between deployment and initialization, allowing attackers to claim ownership or re-initialize the contract.
2. **Storage Collision:** Occurs when the proxy and the implementation contract attempt to store different variables in the same storage slot. Because `delegatecall` executes in the context of the proxy's storage, an implementation variable can overwrite critical proxy metadata (e.g., the Admin address).
3. **Function Selector Clashing:** A conflict where two different functions (one in the proxy, one in the implementation) produce the same 4-byte selector, causing unintended function execution.
4. **Delegatecall to Arbitrary/Self-Destruct Contracts:** Forwarding calls to user-controlled addresses or implementation contracts containing `selfdestruct` opcodes can result in total loss of contract state and funds.
5. **Unchecked Return Values:** `delegatecall` returns a boolean status rather than reverting automatically. If the result is not checked, the system may assume a failed call succeeded.
## Exploitation
- **Status:** Exploited in the wild (Audius, Wormhole).
- **Complexity:** Medium to High (Requires understanding of EVM storage layout and selector generation).
- **Attack Vector:** Network (Blockchain Transaction).
## Impact
- **Confidentiality:** Low (Most data is public on-chain).
- **Integrity:** Critical (Attackers can overwrite admin roles and steal funds via storage manipulation).
- **Availability:** Critical (Attacks utilizing `selfdestruct` can permanently disable the contract).
## Remediation
### Patches
- **OpenZeppelin:** Update to version 4.4.1 or higher to utilize improved `Initializable` logic.
- **Proxy Admin:** Use the **Transparent Proxy Pattern** to prevent selector clashing by segregating admin and user calls.
### Workarounds
- **Initialization:** Include a call to the initializer within the same transaction as the deployment (Atomicity).
- **Storage Protection:** Adopt the "Unstructured Storage" pattern (EIP-1967) to place critical variables in high-index, pseudo-random slots.
## Detection
- **Indicators of Compromise:** Unexpected changes in contract ownership, unauthorized state transitions, or successful calls to `initialize()` on already-active contracts.
- **Detection Methods and Tools:**
- **Slither:** Effective for detecting function clashing and uninitialized contracts.
- **sol2uml:** Used to visualize and compare storage slots between proxy and implementation to find collisions.
- **Unit Testing:** Specifically testing if `initialize()` can be called a second time (Re-initialization test).
## References
- **OpenZeppelin Advisory:** hxxps[://]github[.]com/OpenZeppelin/openzeppelin-contracts/security/advisories
- **EIP-1967 Standard:** hxxps[://]eips[.]ethereum[.]org/EIPS/eip-1967
- **Audius Post-Mortem:** hxxps[://]blog[.]audius[.]co/article/audius-governance-incident-post-mortem