tx.origin安全问题总结

  • 小驹
  • 更新于 2022-07-04 09:41
  • 阅读 2372

在合约代码中,最常用的是使用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账户和合约账户

以太坊账户分两种,外部账户(EOA)和合约账户(SCA)。

  1. 外部账户由一对公私钥进行管理,账户包含着 Ether 的余额。
  2. 合约账户除了可以含有 Ether 余额外,还拥有一段特定的代码,预先设定代码逻辑在外部账户或其他合约对其合约地址发送消息或发生交易时被调用和处理。

外部账户 EOA

  • 由公私钥对控制
  • 拥有 ether 余额
  • 可以发送交易(transactions)
  • 不包含相关执行代码

合约账户

  • 拥有 ether 余额
  • 含有执行代码
  • 代码仅在该合约地址发生交易或者收到其他合约发送的信息时才会被执行
  • 拥有自己的独立存储状态,且可以调用其他合约

msg.sender和tx.origin的区别

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也为零。

<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为黑客)可以这样进行漏洞利用。

  1. 黑客编写一个Attack的合约,并进行部署。
  2. 黑客通过钓鱼等手段诱导Wallet合约的部署者调用Attack合约的attack方法。
  3. 黑客就窃取到了Wallet合约的ETH。

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对此类钓鱼的警惕性会更低些。

所以,黑客为了钓鱼更易成功,可以从下面方面进行增强

  1. 多个合约连接。合约A调用合约B,合约B调用合约C,合约C调用合约D,…………,最后合约中调用wallet.transfer。
  2. 黑客的合约可以利用社会工程学伪装,利用贪便宜的心理,打低价或者免费mint的旗号,或者高息诱惑的方式等。
  3. 黑客可以将漏洞利用隐藏在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

点赞 3
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
小驹
小驹
0xcD46...3461
区块链安全分析,欢迎私信沟通交流