Intro to Smart Contract Audit Series: Phishing With tx.orgin


Prior Knowledge

Let’s begin by examining two common methods for verifying a sender’s address in Solidity.

  • msg.sender: msg.sender will only read the address of the higher-level caller
  • tx.origin: tx.origin will read the original address where the transaction was initiated

As seen in the diagram below, Bob calls contract B through contract A, which then calls contract C.

For Contract B: tx.origin = Bob and msg.sender = contract A.
For Contract C: tx.origin = Bob and msg.sender = contract B.
For Contract A : Bob is both tx.origin and msg.sender

Therefore, tx.origin is always an EOA address, although msg.sender could be either an EOA or a contract address.

Vulnerability Example

I suppose you are already familiar with the distinction between msg.sender and tx.origin, but let’s delve a little deeper with the following vulnerability contract.

// SPDX-License-Identifier: MITpragma 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, "Not owner");
(bool sent, ) ={value: _amount}(""); require(sent, "Failed to send Ether"); }}

Vulnerability Analysis

Evidently, a Wallet contract is a contract wallet that enables the creator to deposit Ether into the contract upon deployment. When you wish to spend your funds, you can transfer any amount by calling Wallet.transfer(). Naturally, the funds in the wallet cannot be accessed by anybody; therefore, you must pass the tx.origin == owner check in order to be able to transfer those funds. This is where the problem arises. As previously stated, tx.origin reads the original address where the transaction was initiated, therefore we can construct a phishing contract to trick the victim into initiating a transaction that will allow us to obtain his identity and transfer out his Ether. Moving further, we will examine how the attack contract is utilized to carry out this identity theft.

I assume that astute users have already identified the flaw in the Wallet contract, which is the re-entry vulnerability we introduced earlier. Referencing back: When tx.origin corresponds to the EOA address of the original caller, the failsafe callback function is invoked. We will not go into further details here; if you are having trouble understanding, please refer to our article titled “Introduction to Smart Contract Security Audits | Reentrancy Attack.”

Attack Contract

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); }}

Let’s start by first analyzing the attack process:

  1. Alice deploys the Wallet contract and transfers ten Ether to it so that it can function as her wallet contract.
  2. Eve discovers funds in the Wallet contract, deploys the Attack contract, and passes the Wallet contract’s address into the constructor.
  3. Eve uses social engineering to investigate Alice’s interest in online bag shopping, deploys a fake shopping website, and emails Alice a link to that site.
  4. Alice received the email, and her curiosity prompted her to click on the link, where she discovered a selection of inexpensive bags that she liked. When she tried to purchase them, she discovered that she needed to connect her wallet and provide signature authorization in order to register successfully.
  5. After successfully signing, Alice discovered that all of her Ether in the Wallet contract had been transferred out.

The rationale behind this attack is actually quite straightforward, so let’s examine what transpired.

Alice’s signature during registration was never used for registration, but rather used to sign a transaction that invoked Attack.attack(). attack.attack() called Wallet.transfer() and passed in a new owner, Eve’s EOA address, as well as the Wallet contract’s Ether balance. The address used to sign the transaction was Alice’s EOA address, and the tx.origin was also Alice’s EOA address for the Wallet contract. Eve is now able to transfer the Ether in the Wallet contract to her own account by using a phishing scam and successfully gain access to Alice’s account.

Suggestions for Repair

As a developer: Using tx.origin for authorization poses a phishing risk because tx.origin stacks calls recursively and then finds the address of the original originator (EOA) of the transaction’s call. Consequently, tx.origin is currently only used to determine whether msg.sender is the EOA address and not for authorization verification, which requires the use of msg.sender.

As an auditor: During an audit, you must examine locations in the code where tx.origin is used for authentication and evaluate whether there is a risk of phishing.

Leave a Reply