7. Force
Chain: Goerli
Difficulty: ●●●○○
Level: https://ethernaut.openzeppelin.com/level/0x20B5c742dD8A63400644Ba85dd48E8FDB6908A7A
要求
有些合约就是拒绝你的付款,就是这么任性 ¯\_(ツ)_/¯
这一关的目标是使合约的余额大于0
这可能有帮助:
- Fallback 方法
- 有时候攻击一个合约最好的方法是使用另一个合约.
- 阅读上方的帮助页面, “控制台之外” 部分
分析
1 | // SPDX-License-Identifier: MIT |
首先引入一个知识点 fallback。上一关已经简单介绍过 fallback方法,这次再扩展下。
Solidity
支持两种特殊的回调函数,receive()
和fallback()
,他们主要在两种情况下被使用:
- 接收ETH
- 处理合约中不存在的函数调用(代理合约proxy contract)
接收ETH函数 receive
receive()
只用于处理接收ETH
。一个合约最多有一个receive()
函数,声明方式与一般函数不一样,不需要function
关键字:receive() external payable { ... }
。receive()
函数不能有任何的参数,不能返回任何值,必须包含external
和payable
。
1 | // 定义事件 |
回退函数 fallback
fallback()
函数会在调用合约不存在的函数时被触发。可用于接收ETH,也可以用于代理合约proxy contract
。fallback()
声明时不需要function
关键字,必须由external
修饰,一般也会用payable
修饰,用于接收ETH:fallback() external payable { ... }
。
1 | // fallback |
receive和fallback的区别
receive
和fallback
都能够用于接收ETH
,他们触发的规则如下:
1 | 触发fallback() 还是 receive()? |
简单来说,合约接收ETH
时,msg.data
为空且存在receive()
时,会触发receive()
;msg.data
不为空或不存在receive()
时,会触发fallback()
,此时fallback()
必须为payable
。
receive()
和payable fallback()
均不存在的时候,向合约发送ETH
将会报错。
我们再来看 Force 合约,这里只定义了合约,没有定义 receive
和 fallback
方法,到这里从调用不存在的合约方法触发 fallback 走不通,那还有没有其他方法可以往一个合约转账,还真有一个方法 selfdestruct
。
selfdestruct
命令可以用来删除智能合约,并将该合约剩余ETH
转到指定地址。selfdestruct
是为了应对合约出错的极端情况而设计的。
selfdestruct
使用起来非常简单:
1 | selfdestruct(address payable recipient) |
其中recipient
是接收合约中剩余ETH
的地址。
1 | contract DeleteContract { |
解题
首先打开Console,获取当前关卡合约实例地址
instance
;打开 Remix IDE,创建文件
7_Force.sol
,粘贴以下代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract HackForce {
address levelInstance;
constructor(address _levelInstance) payable {
levelInstance = _levelInstance;
}
function give() external payable {
selfdestruct(payable(levelInstance));
}
}在constructor 填入当前关卡合约实例地址后部署。
VALUE
填入任意值。再调用
give
函数,完成关卡。
后记
在solidity中,如果一个合约要接受 ether,fallback 方法必须设置为 payable
。
但是,并没有发什么办法可以阻止攻击者通过自毁的方法向合约发送 ether, 所以, 不要将任何合约逻辑基于 address(this).balance == 0
之上。