区块链安全

区块链安全第 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 的场景有两种:

  1. 当特定合约收到纯以太币而没有任何其他与交易相关的数据时,就会执行 fallback function;
  2. 当外部地址调用了合约中不存在的函数或者根本不提供数据时,将触发 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/