Como escapar dos contratos inteligentes da embreagem dos ataques de reentrada? Inteligência de dados PlatoBlockchain. Pesquisa vertical. Ai.

Como escapar dos contratos inteligentes da garra dos ataques de reentrada?

Tempo de leitura: 6 minutos

Se olharmos mais de perto os maiores hacks de criptografia e os números de dar água nos olhos perdidos para eles, eles estariam profundamente enraizados nas falhas de codificação.

Uma ocorrência comum de vulnerabilidade de segurança é o ataque Reentrancy. No entanto, o efeito destrutivo causado devido à reentrada incorreta pode não parecer tão simples quanto o lançamento do próprio ataque.

Apesar de ser uma questão familiar e bastante divulgada, o aparecimento do bug de Reentrância em contratos inteligentes é sempre inevitável. 

Com que frequência a vulnerabilidade Reentrancy foi explorada por hackers nos últimos anos? Como funciona? Como impedir que contratos inteligentes percam fundos para bugs de Reentrância? Encontre respostas para essas perguntas neste blog.

Então, em pouco tempo, vamos revisar os maiores ataques de reentrada na memória. 

Alguns dos mais infames hacks de reentrada em tempo real 

Os ataques de reentrada que causaram os efeitos mais devastadores nos projetos acabaram fazendo um desses dois ou mesmo ambos. 

  • Drenar completamente o Ether dos contratos inteligentes
  • Hackers infiltrando-se no código do contrato inteligente

Agora podemos observar alguns casos de ataques de Reentrância e seu impacto. 

2016 de junho: ataque DAO – Ether de 3.54 milhões ou US$ 150 milhões

2020 de abril: Hack Uniswap/Lendf.Me – US$ 25 milhões

Maio 2021: O hack do BurgerSwap – US$ 7.2 milhões

2021 de agosto: CREAM FINANCE hack – US$ 18.8 milhões

Mar 2022: Ola Finance – US$ 3.6 milhões

Jul 2022: Protocolo OMNI – US$ 1.43 milhão

É claro que os ataques de reentrada nunca saíram de moda. Vamos obter insights profundos sobre isso nas passagens a seguir. 

Visão geral do ataque de reentrada

A partir do nome “Reentrância”, que implica “Reentrar de novo e de novo”. O ataque de reentrada envolve dois contratos: o contrato da vítima e o contrato do atacante. 

O contrato do invasor explora a vulnerabilidade de reentrada no contrato da vítima. Ele usa a função de retirada para alcançá-lo. 

O contrato do invasor chama a função de retirada para drenar os fundos do contrato da vítima fazendo chamadas repetidas antes que o saldo no contrato da vítima seja atualizado. O contrato da vítima verificará o saldo, enviará fundos e atualizará o saldo. 

Mas dentro do prazo de envio dos fundos e atualização do saldo no contrato, o contrato do invasor faz a chamada contínua para retirar fundos. Como resultado, o saldo não é atualizado no contrato da vítima até que o contrato do invasor esgote todos os fundos.

A gravidade e o custo da exploração de reentrada alertam para a extrema necessidade de realizar auditorias de contrato inteligentes para descartar a possibilidade de ignorar tais erros. 

Visão ilustrativa do ataque de reentrada

Vamos entender o conceito de ataque de reentrada a partir da ilustração simplificada abaixo. 

Aqui estão dois contratos: O contrato vulnerável e o contrato Hacker

O contrato do hacker faz uma chamada para se retirar do contrato vulnerável. Ao receber a chamada, o contrato vulnerável verifica os fundos no contrato do hacker e depois transfere os fundos para o hacker. 

O hacker recebe os fundos e implementa a função de fallback, que chama novamente o contrato vulnerável antes mesmo que o saldo seja atualizado no contrato vulnerável. Repetindo assim a mesma operação, o hacker retira os fundos completamente do contrato vulnerável. 

Como escapar dos contratos inteligentes da garra dos ataques de reentrada?

Recursos da função de fallback usada pelo invasor 

  • Eles são chamados externamente. Ou seja, eles não podem ser chamados de dentro do contrato em que estão escritos
  • Função sem nome
  • A função de fallback não inclui lógica arbitrária dentro dela
  • O fallback é acionado quando o ETH é enviado para seu contrato inteligente anexo e nenhuma função receive() é declarada.

Analisando o ataque de reentrada de uma visão técnica 

Vamos pegar um contrato de amostra e entender como ocorre o ataque de reentrada.

Contrato malicioso

contract Attack {
    DepositFunds public depositFunds;

    constructor(address _depositFundsAddress) {
        depositFunds = DepositFunds(_depositFundsAddress);
    }

    // Fallback is called when DepositFunds sends Ether to this contract.
    fallback() external payable {
        if (address(depositFunds).balance >= 1 ether) {
            depositFunds.withdraw();
        }
    }

    function attack() external payable {
        require(msg.value >= 1 ether);
        depositFunds.deposit{value: 1 ether}();
        depositFunds.withdraw();
    }


}

Este é o contrato do atacante em que o atacante deposita 2ETH. O invasor chama a função de retirada no contrato vulnerável. Uma vez que os fundos são recebidos do contrato vulnerável, a função de fallback é acionada. 

O fallback então executa a função de retirada e drena o fundo do contrato vulnerável. Este ciclo continua até que os fundos estejam completamente esgotados do contrato vulnerável.

Contrato vulnerável

contract DepositFunds {
    mapping(address => uint) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw() public {
        uint bal = balances[msg.sender];
        require(bal > 0);

        (bool sent, ) = msg.sender.call{value: bal}("");
        require(sent, "Failed to send Ether");

        balances[msg.sender] = 0;
    }


}

O contrato vulnerável tem 30ETH. Aqui, a função retirar() envia o valor solicitado ao invasor. Como o saldo não é atualizado, os tokens são transferidos repetidamente para o invasor. 

Tipos de ataques de reentrada

  • Reentrância de função única 
function withdraw() external {
   uint256 amount = balances[msg.sender];
   require(msg.sender.call.value(amount)());
   balances[msg.sender] = 0;
}

O msg.sender.call.value(amount)() transfere os fundos após o qual a função de fallback do contrato do invasor chama o gets() novamente antes que balances[msg.sender] = 0 seja atualizado.

  • Reentrância entre funções
function transfer(address to, uint amount) external {
   if (balances[msg.sender] >= amount) {
       balances[to] += amount;
       balances[msg.sender] -= amount;
   }
}
function withdraw() external {
   uint256 amount = balances[msg.sender];
   require(msg.sender.call.value(amount)());
   balances[msg.sender] = 0;
}

A reentrância entre funções é muito mais complexa de identificar. A diferença aqui é que a função de fallback chama transfer, ao contrário da reentrância de função única, onde ela chama a retirada.

Prevenção contra ataques de reentrada

Padrão de Verificações-Efeitos-Interações: O padrão checks-effects-interactions ajuda na estruturação das funções. 

O programa deve ser codificado de forma que verifique primeiro as condições. Uma vez aprovadas as verificações, os efeitos no estado dos contratos devem ser resolvidos, após o que as funções externas podem ser chamadas. 

function withdraw() external {
   uint256 amount = balances[msg.sender];
   balances[msg.sender] = 0;
   require(msg.sender.call.value(amount)());
}

O código reescrito aqui segue o padrão checks-effects-interactions. Aqui o saldo é zerado antes de fazer uma chamada externa. 

Uso de modificador

O modificador noReentrant aplicado à função garante que não haja chamadas reentrantes. 

contract ReEntrancyGuard {
    bool internal locked;

    modifier noReentrant() {
        require(!locked, "No re-entrancy");
        locked = true;
        _;
        locked = false;
    }
}

No final

A etapa mais eficaz é realizar auditorias de contrato inteligente de uma empresa de segurança líder como a QuillAudits, em que os auditores ficam de olho na estrutura do código e verificam o desempenho da função de fallback. Com base nos padrões estudados, são dadas recomendações para reestruturar o código caso pareça haver algum comportamentos vulneráveis

A segurança dos fundos é garantida logo antes de ser vítima de quaisquer perdas. 

Perguntas Frequentes:

O que é um ataque de reentrada?

Um ataque de reentrada acontece quando uma função no contrato vulnerável faz uma chamada para um contrato não confiável. O contrato não confiável será o contrato do invasor fazendo chamadas recursivas para o contrato vulnerável até que os fundos sejam completamente drenados. 

O que é um reentrante?

O ato de reentrar significa interromper a execução do código e iniciar novamente o processo, o que também é conhecido como reentrar.

O que é um guarda de reentrada?

O guarda de reentrância usa um modificador que impede que a função seja chamada repetidamente. Leia o blog acima para encontrar o exemplo de guarda de reentrada.

Quais são alguns dos ataques a contratos inteligentes?

Os contratos inteligentes estão expostos a inúmeras vulnerabilidades, como reentrância, dependência de carimbo de data/hora, estouros aritméticos, ataques DoS e assim por diante. Portanto, a auditoria é essencial para garantir que não haja bugs que destruam a lógica do contrato.

69 Visualizações

Carimbo de hora:

Mais de Quilhash