O ataque de reentrância foi o responsável por inúmeros hacks, incluindo o da DAO em 2016. Esse tipo de ataque ocorre quando um contrato realiza uma chamada externa para um endereço não confiável sem antes modificar seu próprio estado. O contrato não confiável pode então invocar recursivamente a função de onde foi chamada.
Por exemplo, uma função de saque que envia ETH antes de atualizar o saldo do usuário pode ser explorada se o endereço do destinatário for um contrato com uma função fallback que chame novamente a função de saque.
Exemplo: Vamos ver um exemplo onde um ataque de reentrância é possível.
Neste exemplo, usando a função withdraw(), um usuário pode sacar o saldo de ether que ele havia depositado anteriormente no contrato. A função lê o saldo do usuário, envia o valor correspondente em ether para o endereço que fez a chamada e, por fim, zera o saldo desse endereço.
Como sabemos, é possível criar uma função fallback que receba ether e execute algum código. Assim, um atacante pode implantar um contrato com uma função fallback como a seguinte:
//Código usado por un atacante address attackedAddress =0x1234;functionattack()publiconlyOwner{ attackedAddress.withdraw();}function()externalpayable{while(attackedAddress.balance >0){ attackedAddress.withdraw();}}
No contrato acima, attackedAddress é o endereço do contrato que possui ether e será atacado. Um atacante pode implantar esse contrato e iniciar o ataque utilizando a função attack(), que drenará todos os fundos do contrato atacado.
Mitigação:
Usar o padrão de “Retirada” (withdraw pattern) em vez do padrão de “Envio” (send pattern).
Atualizar o estado do contrato antes de realizar chamadas externas.
Limitar o uso de chamadas externas e utilizar bibliotecas de segurança como OpenZeppelin.