返回论坛

智能合约安全审计:tx.origin 钓鱼攻击深度剖析

查找币 学术研究 安全研究 Web3安全 区块链安全

查找币安全研究院

钱包恢复评估 | 链上取证分析 | Web3 事件响应
以合法授权、证据保全、隐私保护和可复核流程为前提,不要求用户在线提交完整私钥或助记词。

查看研究院 研究报告中心
## 前言 在智能合约安全领域,身份验证机制的设计缺陷常常成为攻击者的突破口。继上一期我们探讨了拒绝服务攻击后,本文将聚焦于一种利用 `tx.origin` 进行钓鱼攻击的经典漏洞。这种攻击手法不仅隐蔽性强,而且在实际区块链生态中屡见不鲜,值得我们深入剖析。 ## 核心概念:msg.sender 与 tx.origin 的本质差异 在 Solidity 中,`msg.sender` 和 `tx.origin` 是两个用于识别调用者身份的关键变量,但它们的行为存在根本性区别: - **msg.sender**:仅返回当前调用的直接发起者地址。如果调用链中涉及多个合约,`msg.sender` 会逐层更新。 - **tx.origin**:始终返回整个交易链的原始发起者地址,即外部账户(EOA,Externally Owned Account)。 ### 调用链示例 假设 Bob 通过合约 A 调用合约 B,合约 B 再调用合约 C: - 对于合约 C:`tx.origin` = Bob(EOA),`msg.sender` = 合约 B - 对于合约 B:`tx.origin` = Bob(EOA),`msg.sender` = 合约 A - 对于合约 A:`tx.origin` = Bob(EOA),`msg.sender` = Bob(EOA) **关键结论**:`tx.origin` 永远是 EOA 地址,而 `msg.sender` 可以是 EOA 或合约地址。这一特性是钓鱼攻击得以实施的技术基础。 ## 漏洞合约详解 ### 漏洞合约代码 ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; 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"); } } ``` ### 漏洞分析 `Wallet` 合约实现了一个简单的钱包功能:部署时指定所有者,允许所有者通过 `transfer()` 函数转移合约中的以太币。权限校验逻辑为 `require(tx.origin == owner, "Not owner")`,即仅验证交易的原始发起者是否为所有者。 **漏洞根源**:使用 `tx.origin` 进行权限校验。由于 `tx.origin` 始终指向交易的原始 EOA 地址,攻击者可以诱导该 EOA 地址发起恶意交易,从而绕过权限检查。 **附加漏洞提示**:该合约还存在重入漏洞风险(参见本系列第一期《智能合约安全审计入门篇之重入漏洞》),因为 `fallback` 回调函数被调用时,`tx.origin` 依然保持为原始调用者地址。 ## 攻击合约与攻击流程 ### 攻击合约代码 ```solidity 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); } } ``` ### 完整攻击流程 1. **受害者部署钱包合约**:Alice 部署 `Wallet` 合约并向其中转入 10 ETH,合约所有者设为 Alice 的 EOA 地址。 2. **攻击者部署攻击合约**:Eve 部署 `Attack` 合约,并在构造函数中传入 Alice 的 `Wallet` 合约地址。 3. **社会工程学诱导**:Eve 通过钓鱼邮件或虚假网站,诱使 Alice 访问一个精心设计的“购物网站”。 4. **交易签名**:Alice 在虚假网站上连接钱包并签名交易,误以为是在进行网站注册或购买操作。 5. **资金转移**:Alice 签名的交易实际上是调用 `Attack.attack()` 函数。该函数调用 `Wallet.transfer()`,将 `Wallet` 合约中的全部余额转移至 Eve 的地址。由于交易是由 Alice 的 EOA 发起的,`tx.origin` 等于 Alice 的地址,权限校验通过。 ### 攻击原理详解 攻击成功的关键在于 `tx.origin` 的不可篡改性。即使 `Attack` 合约作为中间调用者,`Wallet` 合约中的 `tx.origin` 仍然指向 Alice 的 EOA 地址。因此,Eve 成功盗用了 Alice 的身份,通过了权限检查。 ## 修复建议 ### 开发者视角 **绝对避免使用 `tx.origin` 进行权限校验**。`tx.origin` 仅适用于一种场景:验证 `msg.sender` 是否为 EOA 地址(例如防止合约调用)。正确的做法是使用 `msg.sender` 进行身份验证: ```solidity function transfer(address payable _to, uint _amount) public { require(msg.sender == owner, "Not owner"); // 其他逻辑 } ``` ### 审计者视角 在智能合约审计过程中,需要重点关注以下方面: 1. **搜索 `tx.origin` 的使用**:全局搜索代码中所有 `tx.origin` 的出现位置。 2. **分析使用场景**:判断 `tx.origin` 是否被用于权限校验或关键逻辑判断。 3. **评估钓鱼风险**:如果 `tx.origin` 被用于鉴权,评估是否存在被钓鱼攻击的可能性。 4. **验证调用链**:检查合约的调用链,确保没有中间合约可以操控 `tx.origin` 的值。 ## 总结 `tx.origin` 钓鱼攻击是智能合约安全中一种经典且危险的漏洞。它不仅展示了 Solidity 语言特性与安全设计之间的微妙关系,也提醒我们在开发过程中必须严格遵循最小权限原则。作为开发者,应始终使用 `msg.sender` 进行身份验证;作为审计者,需要保持对 `tx.origin` 使用的高度警惕。 本文由查找币安全团队整理发布
在论坛中查看和回复