Use of tx.origin
Overview
- Severity: Low
- Confidence: High
- Affected Versions: All
What is the Any tx.origin vulnerability?
The Solidity language offers a number of special objects with properties specific to the context of execution within the Ethereum Virtual Machine (EVM), including properties of messages, blocks, and transactions. Among the latter, the tx.origin
property gives the address of the originating call in a chain of calls, as opposed to msg.sender
which gives the directly-preceding caller's address. Because of the possibility of call redirection, uses of tx.origin
are generally considered potentially risky, particularly if they are ever used for authorization purposes. In general, it is preferred to use msg.sender
where logic permits.
Further reading: Solidity Documentation: Block and Transaction Properties
Technical example of vulnerable code
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract SimpleWallet {
address public owner;
constructor(address _owner) {
owner = _owner;
}
function deposit() external payable {}
function sendToAddress(address destination, uint256 amount) external {
if (tx.origin != owner) {
revert("Unauthorized");
}
payable(destination).transfer(amount);
}
// Helper function to check the contract's Ether balance
function getBalance() public view returns (uint256) {
return address(this).balance;
}
// Other functionality omitted for brevity
}
contract Attack {
SimpleWallet public vulnerableWallet;
constructor(address _vulnerableWalletAddress) {
vulnerableWallet = SimpleWallet(_vulnerableWalletAddress);
}
// This function is called by the owner of the vulnerable wallet, either directly or through phishing.
function exploit(uint256 amount) public {
// Calls the vulnerable contract's withdraw function. Since the vulnerable contract checks tx.origin,
// which would still be the owner of the wallet, this check passes, allowing the withdrawal.
vulnerableWallet.sendToAddress(address(this), amount);
// Ether is sent to this contract. Implement logic here to forward it to attacker's address.
}
// Receive function to accept Ether from the vulnerable wallet.
receive() external payable {}
// Functionality to extract the stolen funds omitted for brevity
}
In the above example, contract SimpleWallet
implements basic functionality for ether management for a single owner. It provides a function sendToAddress()
which is intended to allow only the owner to send some of the contract's ether balance to a target, but it uses tx.origin
to manage the authorization for such transfers. As a result, if the author of the Attack
contract can trick the owner into calling the exploit()
function, the attacker can send the wallet's ether to themselves.
Technical example of how to fix the vulnerability
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract SimpleWalletUpdated {
address public owner;
constructor(address _owner) {
owner = _owner;
}
function deposit() external payable {}
function sendToAddress(address destination, uint256 amount) external {
if (msg.sender != owner) {
revert("Unauthorized");
}
payable(destination).transfer(amount);
}
// Helper function to check the contract's Ether balance
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
In the revised example, contract SimpleWalletUpdated
now uses the msg.sender
property to authorize transfers made by the sendToAddress()
function, avoiding the possible exploit due to redirection of a call where the tx.origin
value was the owner's address.