[原]智能合约安全(一):以太坊机制及安全问题
2021-09-26 17:41:00 Author: blog.csdn.net(查看原文) 阅读量:71 收藏

在本系列中,我们将对以太坊现有的安全问题和前沿的各类型漏洞挖掘方法进行综述。本文是本系列的第一篇文章,主要介绍以太坊的机制和存在的安全问题的分类。

以太坊智能合约基于区块链(Blockchain)技术,作为一种旨在以信息化方式传播、验证或执行的计算机协议,为各类分布式应用服务提供了基础。简单来讲:如果把比特币看作是分布式的记账本;以太坊便是可以运行程序的分布式计算平台,程序运行的基础则是Solidity智能合约。

智能合约早在1995年就由尼克萨博提出,目的在于以数字形式定义一个合同,当参与方执行且满足合同所需的条件时,计算机自动执行该合同内容。但是由于技术条件等限制,这一概念当时并未实现。随着近年来区块链(Blockchain)技术的逐渐成熟以及加密货币的快速发展,由此进入了以智能合约技术为标志的区块链2.0时代。

智能合约不只是一个可以在区块链上被自动执行的程序,它本身就是一个系统参与者,可以对信息进行接收和回应,也可以存储和收发资产。支持智能合约运行的区块链平台很多,比如EOS,BCOS,Fabric,CITA等,其中规模最大,历史最久也最具影响力的是以太坊(Ethereum)。以太坊通过建立一个图灵完备的,可以允许开发人员编写任意智能合约和去中心化应用(Dapps)的平台而广受欢迎。

随着智能合约应用场景的丰富,如金融、保险、游戏、能源等领域,其中控制的重要金融资产不可避免地成为吸引人攻击的目标。此外,由于智能合约本身编程语言的不安全性以及合约整个生命过程的复杂性,再加上以太坊中每个用户都可以在没有可信赖第三方的情况下参与合约,都为智能合约的安全性带来了巨大的风险和隐患。

比如2017年6月18日,智能合约The DAO中的可重入漏洞最终造成了亿万美元的损失,影响了整个以太坊网络。2020年1月16日,攻击者通过开源项目为RVN加了功能,并通过一些条件判断来绕过审查,从而拥有发币功能,最终获利千万人民币,使得平台遭遇巨大损失。

以太坊智能合约安全漏洞事件的频繁爆发,造成了巨大的经济损失,需要我们对其安全性检查方面进行研究。

智能合约作为一段脚本,可以使用多种高级语言进行开发,比如Solidity,Serpent,Vyper等,其中针对以太坊的智能合约通常采用Solidity编写,其语法类似于JavaScript。智能合约的编译和解释执行则通过安装在每个以太坊节点上的以太坊虚拟机(Ethereum Virtual Machine, EVM)来完成,根据以太坊白皮书,以太坊系统架构如图1所示。每个以太坊节点架构自底向上分别是操作 系统、区块链节点客户端、以太坊虚拟机和智能合约脚本。

图片

图1 以太坊系统架构

以太坊正是通过运行在不同主机上的以太坊客户端节点之间的通信来完成各种操作。编写好的合约首先通过EVM编译器编译生成一个ABI文件和一个bin文件,其中ABI文件是合约的接口描述,包括了字段名称、字段类型、参数名称、参数返回值等信息,而bin文件是最终运行在虚拟机上的字节码(bytecode),即一段EVM指令集合。

合约的部署是将编译后的字节码上传到以太坊区块链平台上,其过程与发送一笔交易类似,发起地址为发布者的地址,目标地址为零,交易数据被替换为合约对应的字节码。在进行交易打包时,将根据发布者的地址和交易序列号通过加密算法重新计算出一个地址作为这个合约的地址,调用者可通过合约地址对合约进行调用。以太坊中存在两类账户:外部账户和合约账户,外部账户由私钥控制,有账户余额,可以触发交易但没有代码;而合约账户则包含不可修改的智能合约代码,有账户余额,但不能主动发起交易,只能在被触发后执行预先编写的逻辑。

因此,合约调用也分为两种类型,一种由外部账户发起称为交易调用,另一种则是由一个合约发起对另一个合约的调用,称为消息调用。此外,智能合约还具有自毁操作,这也是唯一能从区块链上将合约代码移除的方式,它需要在合约编写中执行selfdestruct操作,之后合约账户上剩余的以太币(Ether)会被发送给指定目标,其存储的相关状态和代码也会被移除。而所谓以太币是以太坊中的通用货币,类似于比特币。

    gas机制

合约需要矿工的强制执行和证明,因此在打包一项交易时,即将其添加到一个区块上时,为了避免交易中包含大量循环等操作导致节点资源的浪费,合约需先行向矿工支付一笔费用(Gas)。在以太坊执行一份合约本身也需要根据内部制定的规则消耗一定量的gas,如果交易执行后,提前支付的gas还有剩余,则按照原路返还;如果没有执行结束gas就被耗尽,则会触发一个out-of-gas异常,当前合约程序的所有执行状态都会被回滚,但是因为矿工为了执行相应计算已经付出了算力,所以已经消耗的gas不会被退回。

    委托调用机制

合约可以通过消息调用的机制来调用其他合约,委托调用(delegatecall)就是一种特殊类型的消息调用。它和普通的call指令的区别在于,普通call指令的行为是跳转到被调用合约并在该合约中执行完相应代码再返回执行后续代码,对于调用发起者上下文无影响;而委托调用则相当于把被调用合约中的一段代码拷贝到调用发起者合约的上下文环境中执行,会对调用发起者中的信息作出修改。

    异常传递机制

智能合约的函数调用方式分为内部调用和外部调用,内部函数调用是指直接调用当前合约或者父合约的内部函数,这些函数调用在EVM中会被直接转换为简单的跳转指令;而外部函数调用是指对于指定地址的外部合约函数的调用,需要靠消息调用完成。其中外部调用中的一些低级调用如call,callcode,delegatecall在执行中如果出错抛出异常,该异常不会沿着函数调用栈被传递,而是只能获取一个布尔值来表示成功或者失败。此外,一些转账函数如call.value, send等,当发生转账异常时,也仅返回一个布尔值而不是回滚这个操作。

智能合约的源码公开透明虽然提升了用户对合约的信任度,但也使每一份公开的合约都有可能成为黑客的攻击目标。智能合约的可信度来源于其不可篡改性,但正因如此也使得智能合约无法像传统程序那样通过打补丁等措施修复漏洞。

本文接下来将对当前典型的智能合约漏洞类型进行总结。

根据Atzei等人的一份调查报告,智能合约的安全漏洞可以按照高级语言Solidity、EVM和区块链三个层面进行分类,高级语言Solidity层面的漏洞主要为语言自身设计的缺陷以及开发者在开发过程中引入的错误,EVM层面的安全威胁主要由以太坊智能合约字节码规范和运行机制本身的一些缺陷带来,区块链层面的问题是由区块链本身的很多特性引入。具体如表1中所列。

表1   漏洞分类

分类安全漏洞

高级语言Solidity

整数类型错误

未校验返回值

权限控制问题

拒绝服务

资产冻结

EVM

重入漏洞

短地址攻击

代码注入

区块链

交易顺序依赖漏洞

时间戳依赖漏洞

可预测的随机处理

        可以采取一些措施规避潜在的重入漏洞:先保证改变状态变量的逻辑发生后再允许以太币从合约中转出去;或将以太币发送至外部合约时,使用内置的transfer函数代替call、send函数实现安全的转币操作。

1)整数类型错误

整数类型错误主要包含算数错误、截断错误和符号错误。算术错误包括整数溢出、除数为零和模数为零这三种错误。EVM使用几种固定的长度来表示整数,这代表只能表示一定范围内的数字,一旦整数运算结果超出这个范围就会发生整数溢出。攻击者可以利用整数溢出漏洞跳过某些条件判断或者篡改数据,著名的BEC漏洞事件就是由于在转账过程中发生了整数溢出导致了巨额的代币蒸发。使用提供安全检查的SafeMath数学计算库可以有效避免溢出漏洞。在EVM和Solidity旧版本中,除数为零和模数为零只会导致运算结果为0,并不会触发异常。截断错误是指将一个整数类型数据转换为宽度更短的整数类型数据导致精度的丢失。符号错误是指将一个有符号整数类型数据转换为相同宽度的无符号整数类型数据,可能会导致一个负数变为一个很大的整数。

     2)未校验返回值

以太坊Solidity语言的函数调用和其他高级语言一样一般都会设置返回值,但是对send、call、delegatecall等低级别函数调用失败时不会引起事务回滚操作,而只是在返回值中表示是否发生了异常。攻击者可以通过故意发送失败的操作来导致程序执行与预期设定不同,从而造成智能合约的状态混乱。开发者在开发合约时可以通过对低级别函数调用的返回值校验来确定调用是否成功,从而确保合约能以预先设定的逻辑执行。

3)权限控制问题

Solidity中可以使用四种说明符设置函数和状态变量的可见性,分别为external、internal、public和private。未声明可见性的函数会被默认为public,即该函数不仅允许内部调用还会作为合约对外接口被外部合约调用。如果一些涉及到转账等敏感操作的函数没有声明可见性,就可能使攻击者有机可乘。

4)拒绝服务

拒绝服务(Denial-of- Service, DoS)一般指不可恢复的恶意操作或者可控制的无限资源消耗。针对以太坊合约的 DoS 攻击会导致以太币和Gas的大量消耗,更严重则会导致永久性地无法使用合约。2016年的以太坊游戏The King of the Ether Throne, 攻击者利用了外部函数调用的漏洞,并通过(Unexpected)Revert 发动DoS攻击,导致该游戏运营出现重大问题。

5)资产冻结

由于合约的不可篡改性,如果开发者在进行智能合约开发时,仅设置了接收以太币的功能,但没有设置任何允许以太币转出的操作,或由于某些原因导致以太币无法转出,就会导致合约内的资产被永久冻结。

6)重入漏洞

虽然以太坊智能合约的执行是一个具有原子性和顺序性的事务操作,但是当用户在调用智能合约时,如果被调用合约没有找到被调用的函数或者该合约只接收到以太币而没有其他任何消息时就会调用回退函数(fallback)。攻击者可以通过构造特殊的回退函数来攻击存在重入漏洞的智能合约例如回退函数中包含重新调用被攻击合约中之前向攻击者转账函数的代码,从而实现递归调用并耗尽被攻击合约的资产。著名的导致以太坊硬分叉(ETH/ETC)的 The DAO 事件就跟重入漏洞有关。

7)短地址攻击

短地址攻击漏洞是一种由于未校验用户输入导致的漏洞,攻击者利用虚拟机的自动补全机制,构造末尾为零的地址进行合约调用,并在传入参数时故意将地址(address)末尾的零省去,虚拟机会取发送代币的金额(amount)高位的0对地址补全,同时会将amout低位补0,这样就等效于amount左移翻倍,导致转移的代币数量超出了原来的设定。通过严格检查用户输入,拒绝接受畸形地址,可以有效避免短地址攻击。

8)代码注入

代码注入漏洞由以太坊的委托调用机制引入,委托调用机制中使用的delegatecall指令允许合约在自己的上下文执行其他合约的代码片段。攻击者可以利用该漏洞向合约注入修改合约中重要状态变量等恶意操作的代码。

    9)交易顺序依赖漏洞

一笔交易被传播出去并被矿工认同写入一个区块内需要一定的时间,攻击者可以通过监视网络上依赖于交易顺序的合约,并通过发出他自己的交易来改变当前的合约状态。例如,攻击者可以提交一个悬赏合约,允许用户通过提交难题的答案从该合约获得丰厚的奖励,攻击者可以在提交完悬赏合约后持续监听网络,若有人提交了答案并且此时提交答案的交易还未被确认,攻击者可以立刻发起一个将奖金降低到无限接近于0的交易并提供较高的gas使自己的交易先被矿工处理,提交答案的交易后被处理,这样攻击者支付很少的奖金就可以获得问题的答案。

    10)时间戳依赖漏洞

一些智能合约可以通过参数block.timestamp获得当前区块的时间戳并将其作为判断依据,而时间戳一定范围内可以由矿工来决定。因此,一般情况下认为智能合约中依赖于时间戳的代码若可以允许12分钟的误差,那么该种使用时间戳的方式就是安全的。

11)可预测的随机处理

合约开发者编写随机数生成函数时,有时会利用时间戳(block.timestamp)、区块号(block.number)等与区块有关的一些参数产生随机数,但是区块链上的上述数据都是公开的,这使得生成的随机数是可预测的,从而可能会被攻击者利用。智能合约的开发人员可以使用例如Oraclize等第三方服务获取随机数来避免该问题。



文章来源: https://blog.csdn.net/u011721501/article/details/120491919
如有侵权请联系:admin#unsafe.sh