Web3 中的钓鱼攻击:为什么永远不要使用 tx.origin

  • zealynx
  • 发布于 2023-03-08 21:24
  • 阅读 5

文章详细解释了Solidity中tx.originmsg.sender的区别,强调了tx.origin易受网络钓鱼攻击的风险,并通过一个具体的攻击场景和代码示例展示了漏洞,并提供了使用msg.sender进行访问控制的解决方案。同时,文章也指出了tx.origin的有限合法用例。

了解 tx.origin 和 msg.sender 之间的区别,何时应该使用它们,以及为什么 tx.origin 在 Solidity 中会引发网络钓鱼攻击。

本指南涵盖内容

本文解释了 tx.originmsg.senderSolidity 中的作用、它们之间的区别,以及为什么将 tx.origin 用于授权会使你的智能合约容易受到 网络钓鱼攻击 的影响。我们将通过一个具体的漏洞利用场景来展示简单的修复方法。

tx.origin

tx.origin 返回最初发起交易的 外部拥有账户 (EOA) 的地址。它会追溯整个调用链,直到最初的发送者。

一个有效的用例是:如果你想阻止某个特定地址与你的合约交互,tx.origin 更合适,因为该地址的所有者无法使用中介合约来规避阻止。请注意,这仅阻止单个地址。

msg.sender

msg.sender 是直接调用当前函数的地址。这个地址可以是 EOA 或智能合约。要识别函数的直接调用者,你需要使用全局可用的 msg 对象。

tx.origin 和 msg.sender 有何不同

让我们用一个图示来看看它们在多合约调用链中是如何不同的:

tx.origin vs msg.sender: tx.origin 追溯到原始 EOA,而 msg.sender 始终是直接调用者

msg.sender 始终是直接调用函数的 EOA 或智能合约,而 tx.origin 则追溯到发起整个交易的 EOA 或智能合约。

Solidity 中的网络钓鱼攻击

让我们看一个在 transfer 函数中使用 tx.origin 进行授权的合约:

1contract Wallet {
2    address public owner;
3
4    constructor() payable {
5        owner = msg.sender;
6    }
7
8    function transfer(address payable _to, uint _amount) public {
9        require(tx.origin == owner, "Not owner");
10
11        (bool sent, ) = _to.call{value: _amount}("");
12        require(sent, "Failed to send Ether");
13    }
14}

transfer 函数通过以下代码检查授权:

1require(tx.origin == owner, "Not owner");

正如我们在上图中看到的,tx.origin 查看的是谁发起了整个交易,而不是谁直接调用了 transfer。这意味着,如果真正的所有者发起了交易,调用链中间的恶意合约可以通过此检查。

攻击合约

攻击者将按以下方式利用此漏洞:

1contract Attack {
2    address payable public owner;
3    Wallet wallet;
4
5    constructor(Wallet _wallet) {
6        wallet = Wallet(_wallet);
7        owner = payable(msg.sender);
8    }
9
10    function attack() public {
11        wallet.transfer(owner, address(wallet).balance);
12    }
13}

攻击者部署此合约并指向受害者的 Wallet。然后攻击者诱骗钱包所有者调用 attack()——通过网络钓鱼邮件、恶意 dApp 链接或欺骗性交易。当所有者调用 attack() 时,它会触发 wallet.transfer()。由于 tx.origin 是钱包所有者(他们发起了交易),require 检查通过,所有资金都被转移到攻击者手中。

如何防止网络钓鱼攻击

在你的 访问控制 检查中用 msg.sender 替换 tx.origin。修复后的 transfer 函数:

1function transfer(address payable _to, uint _amount) public {
2    require(msg.sender == owner, "Not owner");
3
4    (bool sent, ) = _to.call{value: _amount}("");
5    require(sent, "Failed to send Ether");
6}

使用 msg.sender,合约会检查谁直接调用了该函数。如果攻击者的合约调用 transfermsg.sender 将是攻击者的合约地址——而不是钱包所有者——并且 require 将会回滚。

主要收获

  • 绝不将 tx.origin 用于授权。它容易受到网络钓鱼攻击,因为它查看的是原始交易发送者,而不是直接调用者。
  • 始终使用 msg.sender 进行访问控制。它能正确识别函数的直接调用者。
  • tx.origin 的有效用例有限——主要用于阻止特定地址,以防止中介合约绕过阻止。
  • Web3 中的网络钓鱼是真实存在的。攻击者可以诱骗用户与恶意合约交互,这些合约会利用 tx.origin 检查来耗尽资金。

关于 Zealynx Security

tx.origin 滥用这样的授权漏洞是可以避免的,但前提是你了解在哪里查找。在 Zealynx Security,我们通过专家审计、模糊测试和有针对性的测试来帮助团队加强智能合约,这些测试可以捕获本文中介绍的漏洞。如果你正在以太坊上构建或在发布前需要安心,请联系我们或探索我们如何提供帮助。

联系我们:

常见问题:tx.origin 网络钓鱼攻击

  1. tx.origin 是否永远安全?

唯一有效的用例是阻止某个特定地址与你的合约交互,因为该地址的所有者无法使用中介合约来绕过阻止。对于任何形式的授权或访问控制,请始终使用 msg.sender

  1. 攻击者如何诱骗所有者调用恶意合约?

常见的攻击方式包括带有恶意 dApp 链接的网络钓鱼邮件、虚假的代币授权请求、伪装成合法交互的欺骗性交易提示,或被篡改的前端界面,将交易路由到攻击者的合约。

  1. 这种攻击能耗尽多重签名钱包吗?

如果多重签名钱包使用 tx.origin 进行授权(这很不寻常),那么是的。然而,大多数多重签名钱包使用 msg.sender 检查并需要多个签名者,这使得它们本身就能抵御这种攻击。

  1. 这种漏洞是否存在于其他区块链语言中?

tx.originmsg.sender 的具体区别是 Solidity 和 EVM 所特有的。然而,其基本原则适用于任何地方:始终验证直接调用者,而不是原始交易发起者。Solana 和 Move 具有不同的授权模型,可以避免这种特定模式。

  1. 审计师如何检测 tx.origin 滥用?

Slither 等静态分析工具会标记出授权检查中任何 tx.origin 的使用。在人工审查期间,审计师会查找 require(tx.origin == ...) 模式,并验证 msg.sender 是否一致地用于所有访问控制决策。


词汇表

术语 定义
网络钓鱼攻击 一种社会工程学技术,攻击者诱骗受害者执行非预期操作,例如调用恶意智能合约函数。
EOA 外部拥有账户 — 由私钥控制的区块链账户,与合约账户相对。
tx.origin 一个 Solidity 全局变量,返回最初发起交易的账户地址。
msg.sender 一个 Solidity 全局变量,返回当前函数的直接调用者的地址。
访问控制 限制哪些地址可以调用智能合约中特定函数的安全机制,防止未经授权的操作。
Solidity 以太坊和 EVM 兼容区块链上编写智能合约的主要编程语言。

查看完整词汇表 →

  • 原文链接: zealynx.io/blogs/tx-orig...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
zealynx
zealynx
江湖只有他的大名,没有他的介绍。