Who Really Called: The tx.origin Authentication Flaw

Cryptocurrency & Blockchain Security Level 4/4 ~7 min October 23, 2026

The challenge

Pembrook's on-chain vault lets only its owner withdraw, and the owner's key has never leaked. Yet the vault was drained after the owner used a new airdrop site that asked them to click Claim. The withdraw guard is doing what the author wrote; the author just asked the wrong question about who is calling. Read the contract and name the expression in the owner check that let a contract the owner merely interacted with spend on their behalf.

What you'll learn

  • Distinguish msg.sender (immediate caller) from tx.origin (transaction signer)
  • Explain how a phishing contract keeps tx.origin equal to the victim
  • Identify tx.origin in an access check as the vulnerability
  • Contrast it with a correct msg.sender ownership check
  • State the rule: authorize with msg.sender, not tx.origin

Skills tested

Smart-contract reviewSolidity semanticsAccess-control analysis

Prerequisites

  • Reading Solidity
  • What a contract call chain is

How it works

Every Solidity call sees two identities. msg.sender is whoever called this function directly, which may be another contract. tx.origin is the externally owned account that signed the whole transaction, unchanged no matter how many contracts the call passes through.

Authorising on tx.origin is phishable. If the owner is lured into calling a malicious contract, and that contract calls the vault, then inside the vault msg.sender is the malicious contract but tx.origin is still the owner. A check of tx.origin == owner passes, and the attacker's code spends as the owner without ever touching the owner's key.

The same contract's transferOwnership shows the correct form, comparing msg.sender, which cannot be driven through an intermediary.

Common mistakes

  • Answering msg.sender. That is the safe check, used correctly in transferOwnership.
  • Blaming a leaked key. No key leaked; the owner was tricked into originating the transaction.
  • Pointing at transfer or the balance check. Those are fine; the flaw is in how the owner is identified.
  • Calling it reentrancy. Nothing re-enters; the single call simply passes a phishable guard.

How to defend against it

Identify callers with msg.sender.

  • Replace tx.origin == owner with msg.sender == owner in every access check.
  • Avoid tx.origin for authorization entirely. Its few legitimate uses are discouraged, and account abstraction further erodes the notion of a single origin.
  • Add tests that call privileged functions through an intermediary contract, so a phishable guard fails in CI.

Full solution

Pro and Max members unlock the complete step-by-step walkthrough.

Go Pro

Community stats

192 completions
86% success rate
M2F14M3 First blood

Related Daily Hacks

38,000+ Hackers Real labs Free
Start Hacking Free or solve today's hack, no account needed