摘要:重入攻击本质上与编程里的递归调用类似,当合约将以太币发送到未知地址时就可能会发生,威胁以太坊智能合约的安全性。知道创宇区块链安全实验室?从转账方法、fallback 函数、漏洞代码、源码分析四个方面入手,深入分析攻击原因,详解?The?DAO 事件。
智能合约(英文:Smart contract )的概念于 1995 年由 Nick Szabo 首次提出,它是一种旨在以信息化方式传播、验证或执行合同的计算机协议,它允许在没有第三方的情况下进行可信交易,这些交易可追踪且不可逆转。
然而智能合约也并非是安全的,其中?重入 (Re-Entrance) 攻击?漏洞是以太坊中的攻击方式之一,早在 2016 年就因为 The DAO 事件而造成了以太坊的硬分叉。
在以太坊中,智能合约能够调用其他外部合约的代码,由于智能合约可以调用外部合约或者发送以太币,这些操作需要合约提交外部的调用,所以这些合约外部的调用就可以被攻击者利用造成攻击劫持,使得被攻击合约在任意位置重新执行,绕过原代码中的限制条件,从而发生重入攻击。重入攻击本质上与编程里的递归调用类似,所以当合约将以太币发送到未知地址时就可能会发生。
简单的来说,发生重入攻击漏洞的条件有 2 个:
调用了外部的合约且该合约是不安全的
外部合约的函数调用早于状态变量的修改
下面给出一个简单的代码片段示例:
上述代码片段就是最简单的提款操作,接下来会给大家详细分析重入攻击造成的原因。
《广东省深入推进资本要素市场化配置改革行动方案》:粤港澳以区块链技术为基础共建征信链:金色财经报道,广东省人民政府昨日印发关于深入推进资本要素市场化配置改革行动方案的通知,方案提出,支持深圳深化央行数字人民币应用创新试点,适时申请扩大试点城市范围,以区块链技术为基础,粤港澳共建征信链,促进粤港澳大湾区征信合作。发展数字绿色金融,利用金融科技推动环境信息披露与共享,探索以区块链为基础的绿色资产交易。此外,方案还提出加快推进外资金融机构落地落户及推进人民币可自由使用和资本项目可兑换先行先试。[2021/9/15 23:25:48]
在正式的分析重入攻击之前,我们先来介绍几个重点知识。
转账方法
由于重入攻击会发送在转账操作时,而 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,不能有效的防止重入攻击。
报告:区块链等技术深入应用为在线娱乐带来发展机遇:7月30日,中国音像与数字出版协会游戏出版工作委员会和中国游戏产业研究院联合发布《2020年1-6月中国游戏产业报告》,报告称,5G、云计算、虚拟化技术、编码技术、人工智能、区块链、数据中心等一系列互联网新技术的深入应用,将为在线娱乐和云端经济带来新的发展机遇。这些新技术将加快云游戏及 AR、VR等多终端游戏的发展和升级迭代,能有效提升用户体验。(界面)[2020/7/30]
fallback 函数
接着我们来讲解下 fallback 回退函数。
回退函数 (fallback function):回退函数是每个合约中有且仅有一个没有名字的函数,并且该函数无参数,无返回值,如下所示:
function() public payable{???
? ? ? ...
}
回退函数在以下几种情况中被执行:
调用合约时没有匹配到任何一个函数;
没有传数据;
智能合约收到以太币(为了接受以太币,fallback 函数必被标记为 payable)。
漏洞代码
下面的代码就是存在重入攻击的,实现的是一个类似于公共钱包的合约,所有的用户都可以使用 deposit() 存款到 Reentrance 合约中,也可以从 Reentrance 合约中使用 withdraw() 进行提款,当然了所有人也可以使用 balanceof() 查询自己或者其他人在该合约中的余额。
声音 | 陈思劼:区块链等技术不断深入应用,推动产业发生深刻变革:12月13日下午,第一财经2019杰出商界领军者高峰论坛在上海举行。上海第一财经传媒有限公司总经理陈思劼在会上致辞。陈思劼表示:新一轮的科技革命正在悄然到来,众多产业被新科技激发活力,比如人工智能、大数据、区块链和云计算等技术,在行业不断深入应用,推动产业发生深刻变革。新技术的发展在新经济时代势不可挡,企业的创新升级势在必行。[2019/12/13]
首先使用一个账户 (0x5B38Da6a701c568545dCfcB03FcB875f56beddC4) 扮演受害者,将该合约在 Remix IDE?点击 Deploy 按钮进行部署。
在部署合约成功后在 VALUE 设置框中填写 5,将单位改成 ether,点击 deposit 存入 5 个以太币。
点击 wallet 查看该合约的余额,发现余额为 5 ether,说明我们的存款成功。
而下面的代码则是针对上面存在漏洞的合约进行的攻击:
声音 | 中国互金协会副秘书长:我国区块链在金融领域应用总体处于早期尝试向深入探索转变的过渡阶段:金色财经报道,11月20日,“金融进化论:2019新京报金融科技论坛”上,中国互金协会副秘书长杨农表示,当前,我国区块链在金融领域应用,已在供应链金融、金融数据共享和金融信息存证等场景中形成了一些落地案例,总体处于早期尝试向深入探索转变的过渡阶段,且呈现出探索性应用数量较多、参与主体较为多元、底层技术原创有待加强等特点。同时,区块链在我国金融领域的应用探索也面临着如下几个方面的挑战:技术层面仍难以兼顾部分金融应用场景对安全、功能和性能的要求;研发层面自主创新有待加强,对国外开源程序的广泛应用可能导致技术依赖风险;业务层面部分金融应用环节尚存在模糊地带,部分机构在开展区块链应用创新时缺少权威的第三方评估意见作为参考;治理层面存在法律规制困难,如链上资产和智能合约等方面的法律有效性界定不清晰。[2019/11/22]
使用另外一个账户 (0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2) 扮演攻击者,复制存在漏洞的合约地址到 Deploy 的设置框内,点击 Deploy 部署上面的攻击合约。
动态 | 2019年7月国内一级市场区块链融资10强发布 区块链行业的发展不断向产业端深入:据中国金融信息网报道,8月8日,中国金融信息网与烯牛数据联合发布2019年7月份国内一级市场区块链项目融资10强榜单。从榜单来看,在整体融资大幅下滑的趋势下,2019年7月份国内区块链项目融资数量出现回升。监测数据显示,数字货币和交易平台类融资项目明显增多,天秤币白皮书的发布在一定程度上提振了市场预期,但是此类项目将面临与天秤币的直接竞争,其商业逻辑未来或将受到天秤币严酷打压。与实体经济结合的项目依然占据着榜单的主流,分别涉及金融、大数据、交通运输、电商、传媒、产权、房地产和游戏领域。从连续发布的榜单来看,区块链行业的发展不断向产业端深入,应用端的开发落地越来越具体,与实体经济结合发展的想象空间逐渐立体化。从技术角度来看,加密、数据库、共识、P2P和智能合约的发展日益成熟,区块链作为集成性创新技术的发挥边界正在逐步拓展。[2019/8/8]
部署成功后先调用 wallet() 函数查看攻击合约的余额为 0。
攻击者先存款 1 ether 到漏洞合约中,这里设置 VALUE 为 1 ether,之后点击攻击合约的 deposit 进行存款。
再次调用合约的 wallet 函数查看漏洞合约的余额,发现已经变成了 6 ether。
攻击者 (0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2) 调用攻击合约的 attack 函数模拟攻击,之后调用被攻击合约的 wallet 函数去查看合约的余额,发现已经归零,此时回到攻击合约查看余额,发现被攻击合约中的 6 ether 已经全部提款到了攻击者合约中,这就造成了重入攻击。
源码分析
上面讲解了如何进行重入攻击已经漏洞原因,这里梳理了漏洞源码和攻击的步骤,列出了关键代码。
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.splitBalance) ?/ p.splitData.totalSupply 决定了要转移的代币数量。
下面的代码则是进行提款奖励操作,每次攻击者调用这项功能时 p.splitData 都是一样的(它是 p 的一个属性,即一个固定的值),并且 p.splitData.totalSupply 与 balances[msg.sender] 的值由于函数顺序问题,发生在了转账操作之后,并没有被更新。
paidOut[_account] += reward 更新状态变量放在了问题代码 payOut 函数调用之后。
对 _recipient 发出 .call.value 调用,转账 _amount 个 Wei,.call.value 调用默认会使用当前剩余的所有 gas。
通过上面对重入攻击的分析,我们可以发现重入攻击漏洞的重点在于使用了 fallback 等函数回调自己造成递归调用进行循环转账操作,所以针对重入攻击漏洞的解决办法有以下几种。
使用其他转账函数
在进行以太币转账发送给外部地址时使用 Solidity 内置的 transfer() 函数,因为 transfer() 转账时只会发送 2300 gas 进行调用,这将不足以调用另一份合约,使用 transfer() 重写原合约的 withdraw() 如下:
先修改状态变量
这种方式就是确保状态变量的修改要早于转账操作,即 Solidity 官方推荐的检查-生效-交互模式 (checks-effects-interactions)。
使用互斥锁
互斥锁就是添加一个在代码执行过程中锁定合约的状态变量以防止重入攻击。
使用?OpenZeppelin 官方库
OpenZeppelin 官方库中有一个专门针对重入攻击的安全合约:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol
参考文献
1. 以太坊的几次硬分叉:
https://zhuanlan.zhihu.com/p/111446792
2. 以太坊智能合约安全漏洞 (1):重入攻击:
https://blog.csdn.net/henrynote/article/details/82119116
3.?区块链的那些事 — THE DAO 攻击事件源码分析:
https://blog.csdn.net/Fly_hps/article/details/83095036
郑重声明: 本文版权归原作者所有, 转载文章仅为传播更多信息之目的, 如作者信息标记有误, 请第一时间联系我们修改或删除, 多谢。