WEB3DEV

Cover image for Auditoria de segurança de contrato inteligente assistida por IA
Isabela Curado Nehme
Isabela Curado Nehme

Posted on

Auditoria de segurança de contrato inteligente assistida por IA

16 de março de 2023

https://miro.medium.com/v2/resize:fit:720/format:webp/1*uuXfpsFppUApvfstaDbHlg.jpeg

Os contratos inteligentes desempenham um papel crucial no mundo em rápida evolução das finanças descentralizadas. Esses contratos autoexecutáveis ​​revolucionaram a forma como fazemos transações, oferecendo uma ampla gama de aplicações na blockchain Ethereum. No entanto, eles também apresentam potenciais riscos de segurança. Neste artigo, exploraremos como a IA pode ajudar na realização de auditorias de segurança para contratos inteligentes da Ethereum, fornecendo trechos de código, práticas recomendadas e outros insights valiosos.

Análise estática alimentada por Inteligência Artificial (IA)

Uma das técnicas mais populares para auditoria de segurança de contratos inteligentes é a análise estática. Ferramentas de análise estática alimentadas por IA podem verificar a base de código do contrato, identificando vulnerabilidades e fornecendo sugestões de correção.

Por exemplo, a popular ferramenta de análise de segurança baseada em IA, Mythril, pode ser usada para realizar análises estáticas em seus contratos inteligentes. Para usar o Mythril, primeiro instale-o:

$ pip install mythril
Enter fullscreen mode Exit fullscreen mode

Em seguida, analise seu contrato inteligente:

$ myth analyze <contract.sol>
Enter fullscreen mode Exit fullscreen mode

O Mythril fornecerá informações detalhadas sobre as vulnerabilidades encontradas, juntamente com sugestões sobre como corrigi-las.

Teste de Fuzz automatizado

O teste de fuzz é uma técnica essencial para descobrir vulnerabilidades em contratos inteligentes. Envolve fornecer informações aleatórias, inesperadas ou malformadas a um contrato para desencadear erros ou comportamento não intencional. A IA pode ser usada para gerar entradas de teste de forma inteligente, tornando o processo de teste de fuzz mais eficaz.

Uma dessas ferramentas é o Echidna, um fuzzer alimentado por IA para contratos inteligentes da Ethereum:

$ git clone https://github.com/crytic/echidna.git
$ cd echidna
$ stack install
Enter fullscreen mode Exit fullscreen mode

Crie uma função de teste personalizada em seu contrato, assim:

$ echidna-test <contract.sol>
Enter fullscreen mode Exit fullscreen mode

Testes unitários gerados por IA

Os testes unitários são essenciais para garantir a correção e a segurança dos contratos inteligentes. A IA pode ser usada para gerar testes unitários para seus contratos inteligentes, abrangendo diferentes casos e cenários extremos.

Ferramentas como o DeepTest podem gerar automaticamente casos de teste para seus contratos inteligentes. Para usar o DeepTest, instale-o via npm:

$ npm install -g deeptest
Enter fullscreen mode Exit fullscreen mode

Em seguida, gere casos de teste para seu contrato inteligente:

$ deeptest <contract.sol>
Enter fullscreen mode Exit fullscreen mode

O DeepTest gerará casos de teste que você pode usar para validar a funcionalidade e a segurança do seu contrato.

Melhores Práticas

Ao conduzir auditorias de segurança para contratos inteligentes da Ethereum, siga estas práticas recomendadas:

  • Use bibliotecas e estruturas de contratos inteligentes bem avaliadas, como o OpenZeppelin.
  • Mantenha seus contratos simples e modulares, facilitando a auditoria.
  • Aplique o princípio do menor privilégio, garantindo que as funções do contrato tenham as restrições de acesso apropriadas.
  • Incorpore casos de teste completos e bem documentados para validar o comportamento do seu contrato sob diferentes condições.
  • Atualize continuamente seu conhecimento sobre as melhores práticas de segurança e vulnerabilidades comuns da Ethereum.

Vamos dar um passo adiante com um teste

O contrato inteligente em Solidity a seguir demonstra um exemplo contendo vulnerabilidades como ataques de reentrância, front-running e erros de lógica simples. Este contrato é intencionalmente inseguro e não deve ser usado em um cenário do mundo real.

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract VulnerableContract {
    IERC20 public token;
    address payable public owner;
    mapping(address => uint256) public balances;

    constructor(IERC20 _token) {
        token = _token;
        owner = payable(msg.sender);
    }

    // Esta função é vulnerável a ataques de reentrância.
    function withdraw(uint256 amount) external {
        require(balances[msg.sender] >= amount, "Saldo insuficiente");

        // Vulnerabilidade à reentrância
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Falha na retirada");

        balances[msg.sender] -= amount;
    }

    // Esta função é vulnerável a front-running.
    function buyTokens(uint256 amount) external payable {
        uint256 price = getTokenPrice();
        require(msg.value >= price * amount, "Saldo insuficiente");

        // Vulnerabilidade ao Front-running
        token.transfer(msg.sender, amount);
    }

    // Esta função contém um simples erro de lógica.
    function getTokenPrice() public view returns (uint256) {
        // Erro de lógica: o preço deve ser calculado usando um oráculo válido ou uma fonte confiável.
        return 1 ether;
    }

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

    function() external payable {}
}
Enter fullscreen mode Exit fullscreen mode

Esse contrato demonstra as seguintes vulnerabilidades:

  1. Ataque de reentrância: a função withdraw é vulnerável a ataques de reentrância porque envia Ether ao usuário antes de atualizar o saldo. Um invasor pode explorar essa vulnerabilidade criando uma função de fallback em seu contrato que chama withdraw novamente, drenando efetivamente o Ether do contrato.
  2. Front-running: a função buyTokens é vulnerável a ataques de front-running, uma vez que não possui nenhuma proteção contra manipulação de ordens de transação. Os invasores podem observar as transações pendentes e inserir suas próprias transações com preços de gás mais altos para comprar tokens antes de outros, potencialmente manipulando os preços dos tokens.
  3. Erro de lógica simples: a função getTokenPrice contém um erro de lógica, pois retorna um valor de Ether codificado em vez de usar um oráculo de preços confiável. Isso pode levar a preços incorretos dos tokens e a consequências indesejadas quando os usuários compram tokens.

Novamente, esse contrato é intencionalmente inseguro e não deve ser utilizado em nenhum cenário do mundo real. Serve como um exemplo de como não escrever um contrato inteligente e destaca a importância de realizar auditorias de segurança completas.

Aqui, irei guiá-lo através do processo de auditoria e correção do contrato inteligente usando as ferramentas de IA Mythril, Echidna e DeepTest, onde abordaremos os ataques de reentrância, de front-running e erros de lógica simples.

Auditoria com o Mythril

O Mythril é uma ferramenta popular de análise de segurança que realiza análises estáticas em seus contratos inteligentes. Primeiro, instale-o:

$ pip install mythril
Enter fullscreen mode Exit fullscreen mode

Em seguida, analise seu contrato inteligente:

$ myth analyze <contract.sol>
Enter fullscreen mode Exit fullscreen mode

O Mythril fornecerá informações detalhadas sobre as vulnerabilidades encontradas, juntamente com sugestões sobre como corrigi-las. No nosso caso, ele deve identificar a vulnerabilidade de ataque de reentrância na função withdraw.

Correção de ataque de reentrância

Para evitar ataques de reentrância, podemos usar o padrão Checks-Effects-Interactions (Verificação-Efeitos-Interações). Atualize o saldo antes de transferir os fundos:

function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount, "Saldo insuficiente");

    // Atualiza o saldo primeiro (padrão checks-effects-interactions)
    balances[msg.sender] -= amount;

    // Depois, transfere os fundos
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Falha na retirada");
}
Enter fullscreen mode Exit fullscreen mode

Auditoria com o Equidna

O Echidna é um fuzzer alimentado por IA para contratos inteligentes da Ethereum. Primeiro, instale-o:

$ git clone https://github.com/crytic/echidna.git
$ cd echidna
$ stack install
Enter fullscreen mode Exit fullscreen mode

Crie uma função de teste personalizada em seu contrato, assim:

pragma solidity ^0.8.0;

contract MyContract {
    // ...

    function echidna_test() public view returns (bool) {
        // Adicione sua lógica de teste aqui.
    }
}
Enter fullscreen mode Exit fullscreen mode

Em seguida, execute o Echidna em seu contrato inteligente:

$ echidna-test <contract.sol>
Enter fullscreen mode Exit fullscreen mode

O Echidna identificará vulnerabilidades e possíveis casos extremos que podem levar a problemas como front-running.

Correção do front-runnig

Para evitar o front-running, podemos usar um esquema de commit-reveal (compromisso-revelação) que dissocia a intenção de compra do token da compra real do token. Os usuários se comprometem a comprar tokens enviando um compromisso com hash e posteriormente revelam sua intenção, permitindo que o contrato processe a compra por ordem de compromisso.

// Define uma nova estrutura para compromissos de compra
struct Commitment {
    uint256 amount;
    uint256 price;
    uint256 nonce;
}

mapping(address => bytes32) public commitments;

function commit(uint256 amount, uint256 price, uint256 nonce) external {
    bytes32 hashedCommitment = keccak256(abi.encodePacked(msg.sender, amount, price, nonce));
    commitments[msg.sender] = hashedCommitment;
}

function reveal(uint256 amount, uint256 price, uint256 nonce) external payable {
    bytes32 hashedCommitment = keccak256(abi.encodePacked(msg.sender, amount, price, nonce));
    require(commitments[msg.sender] == hashedCommitment, "Compromisso inválido");

    uint256 totalPrice = price * amount;
    require(msg.value >= totalPrice, "Saldo insuficiente");

    // Transfere os tokens depois de verificar os compromissos e o pagamento.
token.transfer(msg.sender, amount);

    // Apaga o compromisso depois da compra bem sucedida de um token
    delete commitments[msg.sender];
}
Enter fullscreen mode Exit fullscreen mode

Auditoria com o DeepTest

O DeepTest é uma ferramenta alimentada por IA que pode gerar automaticamente casos de teste para seus contratos inteligentes. Primeiro, instale-o via npm:

$ npm install -g deeptest
Enter fullscreen mode Exit fullscreen mode

Em seguida, gere casos de teste para seu contrato inteligente:

$ deeptest <contract.sol>
Enter fullscreen mode Exit fullscreen mode

O DeepTest irá gerar casos de teste que podem ajudá-lo a identificar problemas como o erro de lógica simples na função getTokenPrice.

Correção de erro de lógica simples

Para corrigir o erro de lógica na função getTokenPrice, você deve usar um oráculo de preços confiável, como a Chainlink, para obter um preço exato do token.

Primeiro, instale o pacote Chainlink:

npm install @chainlink/contracts
Enter fullscreen mode Exit fullscreen mode

Em seguida, atualize o contrato para usar o oráculo de preços da Chainlink:

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

contract FixedContract {
    IERC20 public token;
    address payable public owner;
    mapping(address => uint256) public balances;
    AggregatorV3Interface public priceOracle;

    constructor(IERC20 _token, AggregatorV3Interface _priceOracle) {
        token = _token;
        owner = payable(msg.sender);
        priceOracle = _priceOracle;
    }

    // ... (outras funções)

    // Substitui a função getTokenPrice anterior por isto:
    function getTokenPrice() public view returns (uint256) {
        (, int256 price, , , ) = priceOracle.latestRoundData();
        require(price > 0, "Preço de token inválido");
        return uint256(price);
    }
}
Enter fullscreen mode Exit fullscreen mode

Nesta versão revisada do contrato inteligente, os ataques de reentrância são evitados seguindo o padrão de verificações-efeitos-interações, o front-running é mitigado usando um esquema de compromisso-revelação e o erro de lógica na função getTokenPrice é resolvido usando um oráculo de preços confiável. Ao implementar essas mudanças e aderir às melhores práticas, seu contrato inteligente ficará mais seguro.

É importante observar que nenhuma ferramenta automatizada pode garantir a segurança completa de um contrato inteligente. Embora ferramentas de IA como Mythril, Echidna e DeepTest possam ajudar a identificar vulnerabilidades, você também deve realizar revisões manuais de código e considerar a contratação de auditores profissionais para garantir o mais alto nível de segurança para seus contratos inteligentes.

Aqui está o contrato inteligente auditado completo com as vulnerabilidades identificadas corrigidas:

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

contract AuditedContract {
    IERC20 public token;
    address payable public owner;
    mapping(address => uint256) public balances;
    AggregatorV3Interface public priceOracle;

    struct Commitment {
        uint256 amount;
        uint256 price;
        uint256 nonce;
    }

    mapping(address => bytes32) public commitments;

    constructor(IERC20 _token, AggregatorV3Interface _priceOracle) {
        token = _token;
        owner = payable(msg.sender);
        priceOracle = _priceOracle;
    }

    // Evita ataques de reentrância
    function withdraw(uint256 amount) external {
        require(balances[msg.sender] >= amount, "Saldo insuficiente");

        // Atualiza o saldo primeiro (padrão checks-effects-interactions)
        balances[msg.sender] -= amount;

        // Depois, transfere os fundos
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Falha na retirada");
    }

    // Evita ataques de front-running
    function commit(uint256 amount, uint256 price, uint256 nonce) external {
        bytes32 hashedCommitment = keccak256(abi.encodePacked(msg.sender, amount, price, nonce));
        commitments[msg.sender] = hashedCommitment;
    }

    function reveal(uint256 amount, uint256 price, uint256 nonce) external payable {
        bytes32 hashedCommitment = keccak256(abi.encodePacked(msg.sender, amount, price, nonce));
        require(commitments[msg.sender] == hashedCommitment, "Compromisso inválido");

        uint256 totalPrice = price * amount;
        require(msg.value >= totalPrice, "Saldo insuficiente");

        // Transfere os tokens depois de verificar o compromisso e o pagamento.
        token.transfer(msg.sender, amount);

        // Apaga o compromisso depois de uma compra de token bem sucedida.
        delete commitments[msg.sender];
    }

    // Usa um oráculo de preço confiável para corrigir o erro de lógica
    function getTokenPrice() public view returns (uint256) {
        (, int256 price, , , ) = priceOracle.latestRoundData();
        require(price > 0, "Preço de token inválido");
        return uint256(price);
    }

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

    receive() external payable {}
}
Enter fullscreen mode Exit fullscreen mode

Este contrato inteligente aborda as vulnerabilidades discutidas anteriormente ao longo do artigo:

  1. Os ataques de reentrância são evitados seguindo o padrão verificação-efeitos-interações na função withdraw.
  2. Os ataques de front-running são mitigados usando um esquema de compromisso-revelação nas funções commit e reveal.
  3. O erro de lógica na função getTokenPrice é corrigido usando um oráculo de preços confiável, a Chainlink, para obter um preço exato de token.

Este contrato auditado é mais seguro, mas considere sempre realizar revisões manuais de código e consultar auditores profissionais para garantir o mais alto nível de segurança para seus contratos inteligentes.

Conclusão

A auditoria de segurança assistida por IA pode aumentar muito a segurança dos contratos inteligentes da Ethereum, oferecendo informações valiosas e recomendações para melhorias. Ao incorporar ferramentas alimentadas por IA, como Mythril, Echidna e DeepTest, em seu processo de auditoria, você pode identificar e resolver vulnerabilidades de maneira mais eficaz. Além disso, seguir as melhores práticas para o desenvolvimento de contratos inteligentes ajudará a garantir a segurança e o sucesso a longo prazo dos seus aplicativos descentralizados.

Esse artigo foi escrito por Javier Calderon Jr e traduzido por Isabela Curado Nehme. Seu original pode ser lido aqui.

Latest comments (0)