Nesse artigo iremos aprender a como um contrato malicioso de auto-destruição funciona e como previnir um ataque em seu contrato inteligente.
Vulnerabilidade
Um contrato malicioso pode usar do selfdestruct
para forçar o contrato atacado a se auto-destruir e enviar todo o saldo de Ether
(ou o token da rede escolhida) para um contrato designado.
A exploração do ataque por auto-destruição, permite que o contrato B
chame A
chamando a função selfdestruct
passando o endereço do contrato por parâmetro,
e se for bem sucedido, o saldo do contrato A
é enviado para um endereço designado.
Um exemplo de como um ataque funciona:
O objetivo deste jogo é ser o 7º jogador a depositar 1 Ether.
Os jogadores podem depositar apenas 1 Ether por vez.
O vencedor poderá retirar todo o Ether.
- Implante o contrato EtherGame
- Os jogadores (digamos Alice e Bob) decidem jogar, depositam 1 Ether cada
- Implante o ataque com o endereço do EtherGame como parâmetro
- Chame
Attack.attack
enviando 5 ether. Isso vai quebrar o jogo, ninguém pode se tornar o vencedor.
O que aconteceu?
O ataque forçou o equilíbrio do EtherGame a ser igual a 7 ether.
Agora ninguém pode depositar e o vencedor não pode ser definido.
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;contract EtherGame {uint public targetAmount = 7 ether;address public winner;function deposit() public payable {require(msg.value == 1 ether, "Você só pode enviar 1 Ether");uint balance = address(this).balance;require(balance <= targetAmount, "O jogo acabou");if (balance == targetAmount) {winner = msg.sender;}}function claimReward() public {require(msg.sender == winner, "Não é o vencedor");(bool sent, ) = msg.sender.call{value: address(this).balance}("");require(sent, "Falha ao enviar Ether");}}contract Attack {EtherGame etherGame;constructor(EtherGame _etherGame) {etherGame = EtherGame(_etherGame);}function attack() public payable {// Você pode simplesmente quebrar o jogo enviando ether para que// o saldo do jogo >= 7 ether// cast endereço para pagaraddress payable addr = payable(address(etherGame));selfdestruct(addr);}}
Técnicas Preventivas
- Não confie em
address(this).balance
, procure sempre utilizar uma maneira mais segura de retornar e verificar o saldo de um endereço
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;contract EtherGame {uint public targetAmount = 3 ether;uint public balance;address public winner;function deposit() public payable {require(msg.value == 1 ether, "Você só pode enviar 1 Ether");balance += msg.value;require(balance <= targetAmount, "O jogo acabou");if (balance == targetAmount) {winner = msg.sender;}}function claimReward() public {require(msg.sender == winner, "Não é o vencedor");(bool sent, ) = msg.sender.call{value: balance}("");require(sent, "Falha ao enviar Ether");}}