Smart-contract security & auditing

tx.origin phishing

tx.origin phishing exploits a contract that authorizes callers using tx.origin instead of msg.sender. In the EVM, msg.sender is the immediate caller of the current function, while tx.origin is the externally owned account that started the whole transaction chain. When A calls contract B which calls contract C, inside C the msg.sender is B, but tx.origin is still A for the entire chain. A contract that checks require(tx.origin == owner) is therefore asking the wrong question: it confirms who started the transaction, not who is actually calling it.

That gap is exactly what a phishing attacker exploits. The attacker deploys a malicious contract and lures the victim (the owner) into calling it, perhaps disguised as an airdrop claim or a harmless interaction. The malicious contract, now running with the owner as tx.origin, calls the vulnerable target's privileged function. The target checks tx.origin, sees the owner, and approves, so the attacker drains funds or seizes control even though the owner never intended to call the target at all. The owner only had to be tricked into one innocent-looking transaction.

The fix is simple and absolute: authorize with msg.sender, never tx.origin, so authority cannot be borrowed through an intermediary contract. The one historically legitimate use of tx.origin, requiring tx.origin == msg.sender to ensure the caller is a plain account and not a contract, is itself fading. Account abstraction makes ordinary users into smart-contract accounts, so that check now wrongly locks out legitimate users, and it provides no real security benefit. Treat tx.origin as almost never the right tool for authorization.

// VULNERABLE
function transfer(address to, uint256 amt) external {
    require(tx.origin == owner); // wrong: phishable
    _send(to, amt);
}

// SAFE
function transfer(address to, uint256 amt) external {
    require(msg.sender == owner); // immediate caller
    _send(to, amt);
}

Authorize the immediate caller, not the transaction origin.

Use msg.sender for authorization, never tx.origin. The old tx.origin == msg.sender trick to block contract callers is also dying: account abstraction makes legitimate users contracts, so it now locks them out while adding no real safety.

Also called
tx.origin authorization bugtx.origin 授權漏洞