区块链安全第 9 课——重入攻击(一)
- 重入攻击(一):理解 fallback function
- 重入攻击(二):通过实例理解重入攻击
- 重入攻击(三):DAO 攻击及重入攻击的防范
重入攻击(Reentrancy Attacks
)是一种发生在合约调用合约时的攻击,之所以叫重入攻击,是因为这种攻击发生于合约A调用了合约B,最后合约B(经常是通过 fallback function)又调用了合约A的函数,相当于「重入」了合约A的代码流程中。
这种攻击可能导致某个合约的以太币 balance 被耗尽。
1. fallback function
要想理解重入攻击,先得理解 fallback function:
一个智能合约中最多有一个 fallback function。fallback function 匿名、无参数且无返回值。但这些只是看上去的表象。fallback function 有意思的地方在于它的触发场景:既然没有名字,那怎么去调用 fallback function 呢?
触发 fallback function 的场景有两种:
- 当特定合约收到纯以太币而没有任何其他与交易相关的数据时,就会执行 fallback function;
- 当外部地址调用了合约中不存在的函数或者根本不提供数据时,将触发 fallback function。
对于第1种触发场景,可以联想到 <contract address>.send(1 ether)这种转账场景会触发回调函数。Solidity 中的转账函数有三个:transfer()、send()、以及 <address>.call.value(amount) (仅能在较低版本如 0.4.* 中这样使用)。这三种函数的区别参考:Solidity中的转账函数 以及 address 类型的成员函数
代码示例如:
- 使用 transfer(amount)

- 使用 send(amount)

- <address>.call.value(amount)

注:参考 .call.value() 和 .call.value()() 有什么区别。通过 msg.sender.call.value(1 ether)
调用了 Attack 合约的 fallback function。
对于第2种触发场景,主要就是通过 <contract address>.call(bytes4(keccak256("functionSignature")))
方法调用不存在的函数名。
- 代码示例1:

在使用 call() 这种底层函数调用其他合约的函数是,需要函数签名(function signature)要获得函数的编码签名,要使用 Keccak256 来对函数名字符串进行 hash,然后取前 4 个字节。注:1个字节8bit,1个hex需要4bit,所以1个字节可以用两个hex 字符来表示,4个字节就是8个hex字符。
ABI 是以太坊调用合约时候的接口,相当于以 JSON 传参的形式调用 EvmBytecodes,因为 ABI 是 JSON 格式的人类易读代码。上面的 abi.encodeWithSignature("str")
等价 abi.encodeWithSelector(bytes4(keccak256("str
“))
。
拓展阅读:https://ethereum.stackexchange.com/questions/96685/how-to-use-address-call-in-solidity
这样的话调用了一个 Test 合约里面没有的函数,就会调用 fallback function,导致 test.x == 1。
- 代码示例2:

很容易理解,跟上面的示例1一样,只不过没有调用 abi 接口去为函数名字符串计算出编码的函数签名,而是直接 call() 了计算好的签名 hash 前四个字节。
- 代码示例3:

同代码示例1,但是是较高版本 fallback function 的写法。
<address>.call(bytes memory) returns (bool, bytes memory)
issue low-level CALL with the given payload, returns success condition and return data, forwards all available gas, adjustable
2. fallback function 的版本写法差别
上面提到 <address>.call.value(amount)
是较低版本的写法。
在 Solidity 0.6 之前,回退函数只是一个匿名函数,如下所示:
function () external {
}
加上一个 payable 关键字就可以接受转入 ether。
高版本 Solidity fallback function 分为两种:
receive()
接收转入以太币fallback()
仅与智能合约交互
receive() external payable {
}
fallback () external {
}
//但实测可以通过给 fallback 加上 paybale 关键字接收 ether
更多 fallback function 高版本的特性参考:https://ethereum-blockchain-developer.com/028-fallback-view-constructor/02-receive-fallback-function/