3分钟Solidity: 11.8 Phishing with tx.origin

43 阅读1分钟

欢迎订阅专栏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 尝试一下