[Blackbox] Web3_Governance
[Blackbox] Web3_Governance
Platform: caplag | Category: Blockchain | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-02-22 | Status: Solved Techniques: proxy_storage_slot_analysis, push32_string_extraction, evm_bytecode_reverse_engineering, implementation_takeover, uups_uninitialized_implementation
Summary
Task: UUPS proxy with TreasuryImplementation contract. Solution: Implementation contract was never initialized, allowing ownership takeover via direct initialize() call, then upgradeToAndCall() to hijack the proxy.
[Blackbox] Web3_Governance — CapLag CTF
Description
Same infrastructure as Web3_Lending. The governance aspect involves a TreasuryProxy (EIP-1967 proxy pattern) and TreasuryImplementation contract with UUPS upgrade functionality. The implementation contract was deployed but never initialized, leaving it vulnerable to ownership takeover.
Analysis
-
Contract Discovery: From
/runtime/config.json, identified TreasuryProxy (0x68B1D87F95878fE05B998F19b66F4baba5De1aed) and TreasuryImplementation (0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE). -
Bytecode Reverse Engineering: Extracted and analyzed TreasuryImplementation bytecode (2,564 chars / 1,281 bytes). Identified function selectors:
0x4f1ef286→upgradeToAndCall(address,bytes)(UUPS upgrade)0x5b116ab8→sweepETH(address,uint256)(withdraw ETH)0x5c60da1b→implementation()(get current impl)0x8da5cb5b→owner()(ownership check)0xc4d66de8→initialize(address)(initializer)
-
Vulnerability Identification: Classic UUPS pattern vulnerability — the implementation contract itself was never initialized (only the proxy was). Anyone can call
initialize(address)directly on the implementation contract to become its owner, then useupgradeToAndCallto hijack the proxy. -
Flag Discovery: Found the flag embedded in TreasuryImplementation bytecode at offset 1023 as PUSH32 instructions.
Solution
python read-only • copy allowed#!/usr/bin/env python3 """ UUPS Uninitialized Implementation Takeover The vulnerability: - TreasuryProxy delegates calls to TreasuryImplementation - Proxy was initialized (owner set in proxy storage) - But implementation contract itself was NOT initialized - initialize() on implementation has no protection against re-init - Anyone can call implementation.initialize(attacker) directly - Then call implementation.upgradeToAndCall(malicious, data) - This corrupts the proxy's implementation slot → full takeover Exploit steps: 1. Call initialize(attacker_addr) on TreasuryImplementation directly 2. Now attacker is owner of the implementation contract 3. Deploy malicious implementation that drains treasury 4. Call upgradeToAndCall(malicious_impl, drain_calldata) on implementation 5. Proxy now delegates to malicious contract → treasury drained Bytecode analysis approach: - Flag found at TreasuryImplementation bytecode offset 1023 """ from web3 import Web3 w3 = Web3(Web3.HTTPProvider("http://not-web.caplag-task.ru:30647")) impl_addr = "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" # Check if implementation is initialized (owner should be zero) # EIP-1967 implementation slot: 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc owner_slot = 0 # slot 0 typically stores owner in simple contracts owner = w3.eth.get_storage_at(impl_addr, owner_slot) print(f"Implementation owner: {owner.hex()}") # If 0x000...000 → not initialized, vulnerable! # Extract flag from bytecode code = w3.eth.get_code(impl_addr).hex() i = 0 while i < len(code): if code[i:i+2] == '7f': # PUSH32 data = bytes.fromhex(code[i+2:i+66]) text = data.decode('ascii', errors='ignore').strip('\x00') if text and all(32 <= ord(c) < 127 for c in text if c != '\x00'): print(f"Offset {i//2}: {text}") i += 66 else: i += 2 # Output: caplag{REDACTED}