tx.origin 安全问题
在合约代码中,最常用的是使用 msg.sender 来检查授权,但有时由于有些程序员不熟悉tx.origin和 msg.sender 的区别,如果使用了 tx.origin 可能导致合约的安全问题.黑客最典型的攻击场景是利用tx.origin的代码问题常与钓鱼攻击相结合的方式进行攻击。
图解msg.sender和msg.sender 的区别
tx.origin:表示最初的调用者,通常取得的是 EOA 的地址。
msg.sender:表示最近的调用者,通常取得是的上级调用者的地址,可以是 EOA 地址,也可以是合约地址。
如果 EOA 用户 A 调用合约 B,合约 B 调用合约 C。那么
- 在 C 合约中,msg.sender 就是 B 合约的地址,tx.origin 为 A 地址。
- 在 B 合约中,msg.sender 是 A 地址,tx.origin 也为 A 地址。
通过判断tx.origin==msg.sender来确定调用者是合约还是 EOA 账户。
思考 :可不可以通过判断一个账户的是否包含执行代码来区分这个账户是 EOA 还是 SCA?
答案:不可以。因为一个合约地址的 CODESIZE是大于零的,但当地址的 CODESIZE等于零时,并不能保证其为非合约,因为合约在构造阶段 CODESIZE也为零。
代码演示
下面的漏洞合约代码,在 transfer 方法中做了检查,本意是只有 owner 可以进行 transfer 操作。在这里使用的是tx.origin==owner进行检查。我们假设该 Wallet 合约的部署者是 Alice.
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, ) = _to.call{value: _amount}("");
require(sent, "Failed to send Ether");
}
}
假设 Eve 为黑客,写了一个攻击合约对钱包合约进行攻击:
- 黑客编写一个 Attack 的合约,并进行部署。
- 黑客通过钓鱼等手段诱导 Wallet 合约的部署者调用 Attack 合约的 attack 方法。
- 黑客就窃取到了 Wallet 合约的 ETH。
代码如下:
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);
}
}
实施过程: Alice 调用了 Attack 合约的 attack 方法,attack 方法调用了 wallet 合约的 transfer 方法,在 transfer 方法中 tx.origin 是 alice(在 transfer 方法中 tx.sender 是 attack 合约),因为 alice 就是 Wallet 合约的 owner,因此通过检测,将 ETH 转给了黑客 Eve。
思考
黑客如何诱骗Alice去调用攻击合约?
- 嵌套合约 将攻击合约嵌套在NFT、ERC20合约中,对Alice进行空头,让其对合约的奖励进行兑现或者进行转账行为。如:合约 A 调用合约 B,合约 B 调用合约 C,合约 C 调用合约 D,…………,最后合约中调用 wallet.transfer。
- 黑客可以将漏洞利用隐藏在
receive函数中,通过诱导用户向指定的合约转账内触发漏洞利用。如假装与用户进行换币,给客户很大的折扣诱导等。
安全建议
使用msg.sender 代替 tx.origin。确保调用者就 owner。
function transfer(address payable _to, uint256 _amount) public {
require(msg.sender == owner, "Not owner");
(bool sent, ) = _to.call{value: _amount}("");
require(sent, "Failed to send Ether");
}