前语

智能合约(英文:Smart contract )的概念于 1995 年由 Nick Szabo 初次提出,它是一种旨在以信息化方法传达、验证或履行合同的计算机协议,它答应在没有第三方的情况下进行可信交易,这些交易可追踪且不可逆转。但是智能合约也并非是安全的,其中 重入 (Re-Entrance) 进犯缝隙是以太坊中的进犯方法之一,早在 2016 年就由于 The DAO 事情而形成了以太坊的分叉

缝隙概述

在以太坊中,智能合约能够调用其他外部合约的代码,由于智能合约能够调用外部合约或者发送以太币,这些操作需求合约提交外部的调用,所以这些合约外部的调用就能够被进犯者运用形成进犯劫持,使得被进犯合约在恣意位置重新履行,绕过原代码中的限制条件,然后产生重入进犯。重入进犯本质上与编程里的递归调用相似,所以当合约将以太币发送到未知地址时就可能会产生。

简略的来说,产生重入进犯缝隙的条件有 2 个:

  • 调用了外部的合约且该合约是不安全的

  • 外部合约的函数调用早于状态变量的修正

下面给出一个简略的代码片段示例:

深入理解重入攻击漏洞

上述代码片段便是最简略的提款操作,接下来会给大家详细剖析重入进犯形成的原因。

缝隙剖析

在正式的剖析重入进犯之前,咱们先来介绍几个要点常识。

01 转账方法

由于重入进犯会发送在转账操作时,而 Solidity 中常用的转账方法为

<address>.transfer(),<address>.send() 和 <address>.gas().call.vale()(),下面临这 3 种转账方法进行说明:

  • <address>.transfer():只会发送 2300 gas 进行调用,当发送失利时会经过 throw 来进行回滚操作,然后避免了重入进犯。

  • <address>.send():只会发送 2300 gas 进行调用,当发送失利时会回来布尔值 false,然后避免了重入进犯。

  • <address>.gas().call.vale()():在调用时会发送一切的 gas,当发送失利时会回来布尔值 false,不能有效的避免重入进犯。

02 fallback 函数

接着咱们来解说下 fallback 回退函数。
回退函数 (fallback function):回退函数是每个合约中有且仅有一个没有姓名的函数,而且该函数无参数,无回来值,如下所示:

深入理解重入攻击漏洞

回退函数在以下几种情况中被履行:

  • 调用合约时没有匹配到任何一个函数;

  • 没有传数据

  • 智能合约收到以太币(为了接受以太币,fallback 函数必被标记为 payable)。

03 缝隙代码

下面的代码便是存在重入进犯的,完成的是一个相似于公共钱包的合约,一切的用户都能够运用 deposit() 存款到 Reentrance 合约中,也能够从 Reentrance 合约中运用 withdraw() 进行提款,当然了一切人也能够运用 balanceof() 查询自己或者其他人在该合约中的余额。

深入理解重入攻击漏洞

首要运用一个账户 (0x5B38Da6a701c568545dCfcB03FcB875f56beddC4) 扮演受害者,将该合约在 Remix IDE 点击 Deploy 按钮进行布置。

深入理解重入攻击漏洞

在布置合约成功后在 VALUE 设置框中填写 5,将单位改成 ether,点击 deposit 存入 5 个以太币。

深入理解重入攻击漏洞

点击 wallet 查看该合约的余额,发现余额为 5 ether,说明咱们的存款成功。

深入理解重入攻击漏洞

而下面的代码则是针对上面存在缝隙的合约进行的进犯:

深入理解重入攻击漏洞

深入理解重入攻击漏洞

运用别的一个账户 (0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2) 扮演进犯者,复制存在缝隙的合约地址到 Deploy 的设置框内,点击 Deploy 布置上面的进犯合约。

深入理解重入攻击漏洞

布置成功后先调用 wallet() 函数查看进犯合约的余额为 0。

深入理解重入攻击漏洞

进犯者先存款 1 ether 到缝隙合约中,这儿设置 VALUE 为 1 ether,之后点击进犯合约的 deposit 进行存款。

深入理解重入攻击漏洞

深入理解重入攻击漏洞

再次调用合约的 wallet 函数查看缝隙合约的余额,发现现已变成了 6 ether。

深入理解重入攻击漏洞

进犯者 (0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2) 调用进犯合约的 attack 函数模仿进犯,之后调用被进犯合约的 wallet 函数去查看合约的余额,发现现已归零,此时回到进犯合约查看余额,发现被进犯合约中的 6 ether 现已悉数提款到了进犯者合约中,这就形成了重入进犯。

深入理解重入攻击漏洞

04源码剖析

上面解说了如何进行重入进犯现已缝隙原因,这儿梳理了缝隙源码和进犯的步骤,列出了要害代码。

深入理解重入攻击漏洞

相关案例

2016 年 6 月 17 日,TheDAO 项目遭到了重入进犯,导致了 300 多万个以太币被从 TheDAO 财物池中分离出来,而进犯者运用 TheDAO 智能合约中的 splitDAO() 函数重复运用自己的 DAO 财物进行重入进犯,不断的从 TheDAO 项目的财物池中将 DAO 财物分离出来并搬运到自己的账户中。

下列代码为 splitDAO() 函数中的部分代码,源代码在 TokenCreation.sol 中,它会将代币从 the parent DAO 搬运到 the child DAO 中。平衡数组 uint fundsToBeMoved = (balances[msg.sender] * p.splitData[0].splitBalance) / p.splitData[0].totalSupply 决定了要搬运的代币数量。

深入理解重入攻击漏洞

下面的代码则是进行提款奖赏操作,每次进犯者调用这项功用时 p.splitData[0] 都是相同的(它是 p 的一个特点,即一个固定的值),而且 p.splitData[0].totalSupply 与 balances[msg.sender] 的值由于函数次序问题,产生在了转账操作之后,并没有被更新。

深入理解重入攻击漏洞

paidOut[_account] += reward 更新状态变量放在了问题代码 payOut 函数调用之后。

深入理解重入攻击漏洞

对_recipient 宣布 .call.value 调用,转账_amount 个 Wei,.call.value 调用默许会运用当前剩余的一切 gas。

深入理解重入攻击漏洞

解决办法

经过上面临重入进犯的剖析,咱们能够发现重入进犯缝隙的要点在于运用了 fallback 等函数回调自己形成递归调用进行循环转账操作,所以针对重入进犯缝隙的解决办法有以下几种。

01运用其他转账函数

在进行以太币转账发送给外部地址时运用 Solidity 内置的 transfer() 函数,由于 transfer() 转账时只会发送 2300 gas 进行调用,这将不足以调用另一份合约,运用 transfer() 重写原合约的 withdraw() 如下:

深入理解重入攻击漏洞

02先修正状态变量

这种方法便是保证状态变量的修正要早于转账操作,即 Solidity 官方引荐的查看-收效-交互形式 (checks-effects-interactions)。

深入理解重入攻击漏洞

03运用互斥锁

互斥锁便是添加一个在代码履行过程中锁定合约的状态变量以避免重入进犯。

深入理解重入攻击漏洞

04运用 OpenZeppelin 官方库

OpenZeppelin 官方库中有一个专门针对重入进犯的安全合约:

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol

深入理解重入攻击漏洞

深入理解重入攻击漏洞

深入理解重入攻击漏洞

深入理解重入攻击漏洞

视野开拓

It's impotat to ote that although the ability to compete effectively is impotat, it's hadly the key to happiess. Peace is't foud i what we ow o i ou titles, but i the life we live as citizes, paets, ad eighbos. It is ou pesoal hope, above all, that ou gilsad eveyoe) lea this lesso.-《隐性动机》

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注