Back to Blog
Phishing Attack in Web3: Why You Should Never Use tx.origin
SolidityWeb3 SecurityAudit

Phishing Attack in Web3: Why You Should Never Use tx.origin

6 min
3 views
Understand the difference between tx.origin and msg.sender, when each should be used, and why tx.origin opens the door to phishing attacks in Solidity.

What This Guide Covers

This article explains the role of tx.origin and msg.sender in Solidity, how they differ, and why using tx.origin for authorization makes your smart contract vulnerable to phishing attacks. We walk through a concrete exploit scenario and show the simple fix.

tx.origin

tx.origin returns the address of the externally owned account (EOA) that originally initiated the transaction. It traces all the way back through the call chain to the very first sender.
One valid use case: if you want to block a specific address from interacting with your contract, tx.origin is more appropriate because the owner of that address cannot use an intermediary contract to circumvent the block. Note that this only blocks a single address.

msg.sender

msg.sender is the address that directly called the current function. This address could be an EOA or a smart contract. To identify the immediate caller of a function, you use the globally available msg object.

How tx.origin and msg.sender Differ

Let's use an illustration to see how they differ in a multi-contract call chain:
tx.origin vs msg.sender: tx.origin traces back to the original EOA while msg.sender is always the immediate caller
msg.sender is always the EOA or smart contract that directly calls the function, while tx.origin traces back to the EOA or smart contract that initiated the entire transaction.

Phishing Attack in Solidity

Let's look at a contract that uses tx.origin for authorization in its transfer function:
1contract Wallet {
2 address public owner;
3
4 constructor() payable {
5 owner = msg.sender;
6 }
7
8 function transfer(address payable _to, uint _amount) public {
9 require(tx.origin == owner, "Not owner");
10
11 (bool sent, ) = _to.call{value: _amount}("");
12 require(sent, "Failed to send Ether");
13 }
14}
The transfer function checks authorization with:
1require(tx.origin == owner, "Not owner");
As we saw in the illustration above, tx.origin looks at who started the entire transaction — not who directly called transfer. This means a malicious contract in the middle of the call chain can pass this check if the real owner initiated the transaction.

The Attack Contract

Here is how an attacker would exploit this:
1contract Attack {
2 address payable public owner;
3 Wallet wallet;
4
5 constructor(Wallet _wallet) {
6 wallet = Wallet(_wallet);
7 owner = payable(msg.sender);
8 }
9
10 function attack() public {
11 wallet.transfer(owner, address(wallet).balance);
12 }
13}
The attacker deploys this contract pointing at the victim's Wallet. Then the attacker tricks the wallet owner into calling attack() — through a phishing email, a malicious dApp link, or a deceptive transaction. When the owner calls attack(), it triggers wallet.transfer(). Since tx.origin is the wallet owner (they started the transaction), the require check passes and all funds are drained to the attacker.

How to Prevent a Phishing Attack

Replace tx.origin with msg.sender in your access control checks. The fixed transfer function:
1function transfer(address payable _to, uint _amount) public {
2 require(msg.sender == owner, "Not owner");
3
4 (bool sent, ) = _to.call{value: _amount}("");
5 require(sent, "Failed to send Ether");
6}

Are you audit-ready?

Download the free Pre-Audit Readiness Checklist used by 30+ protocols preparing for their first audit.

No spam. Unsubscribe anytime.

With msg.sender, the contract checks who directly called the function. If the attacker's contract calls transfer, msg.sender will be the attacker's contract address — not the wallet owner — and the require will revert.

Key Takeaways

  • Never use tx.origin for authorization. It is vulnerable to phishing because it looks at the original transaction sender, not the direct caller.
  • Always use msg.sender for access control. It correctly identifies the immediate caller of a function.
  • tx.origin has limited valid uses — primarily for blocking specific addresses where intermediary contracts shouldn't bypass the block.
  • Phishing in Web3 is real. Attackers can trick users into interacting with malicious contracts that exploit tx.origin checks to drain funds.

About Zealynx Security

Authorization vulnerabilities like tx.origin misuse are avoidable, but only if you know where to look. At Zealynx Security, we help teams strengthen smart contracts through expert audits, fuzzing, and targeted tests that catch the kinds of exploits covered here. If you're building on Ethereum or need peace of mind before launch, reach out to us or explore how we can help.
Building an EVM contract? Get your access-control logic reviewed before attackers find it first. Request an audit scope →

Connect with us:

FAQ: tx.origin phishing attacks

1. Is tx.origin ever safe to use?
The only valid use case is blocking a specific address from interacting with your contract, because the address owner cannot use an intermediary contract to bypass the block. For any form of authorization or access control, always use msg.sender instead.
2. How does the attacker trick the owner into calling the malicious contract?
Common vectors include phishing emails with links to malicious dApps, fake token approval requests, deceptive transaction prompts disguised as legitimate interactions, or compromised frontend interfaces that route transactions through the attacker's contract.
3. Can this attack drain a multisig wallet?
If the multisig uses tx.origin for authorization (which would be unusual), yes. However, most multisig wallets use msg.sender checks and require multiple signers, making them inherently resistant to this attack vector.
4. Does this vulnerability exist in other blockchain languages?
The specific tx.origin vs msg.sender distinction is unique to Solidity and the EVM. However, the underlying principle applies everywhere: always verify the immediate caller, not the original transaction initiator. Solana and Move have different authorization models that avoid this specific pattern.
5. How can auditors detect tx.origin misuse?
Static analysis tools like Slither flag any use of tx.origin in authorization checks. During manual review, auditors look for require(tx.origin == ...) patterns and verify that msg.sender is used consistently for all access control decisions.

Glossary

TermDefinition
Phishing AttackA social engineering technique where attackers trick victims into performing unintended actions, such as calling malicious smart contract functions.
EOAExternally Owned Account — a blockchain account controlled by a private key, as opposed to a contract account.
tx.originA Solidity global variable that returns the address of the account that originally initiated the transaction.
msg.senderA Solidity global variable that returns the address of the immediate caller of the current function.
Access ControlSecurity mechanisms that restrict which addresses can call specific functions in a smart contract, preventing unauthorized actions.
SolidityThe primary programming language for writing smart contracts on Ethereum and EVM-compatible blockchains.

Are you audit-ready?

Download the free Pre-Audit Readiness Checklist used by 30+ protocols preparing for their first audit.

No spam. Unsubscribe anytime.

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx