APP下载

智能合约的安全代码要求研究★

2022-11-17宾建伟相里朋包小敏

电子产品可靠性与环境试验 2022年5期
关键词:以太代币调用

宾建伟,相里朋,包小敏

(工业和信息化部电子第五研究所,广东 广州 511370)

0 引言

区块链作为一种创新技术,颠覆了原有的商业逻辑和运行规则。区块链的分布式账本技术和共识机制,构建了低成本的互信机制,建立了“去中心化”的交易体系,实现了价值的直接传递,有助于提高运营效率、实现业务场景的创新[1]。在金融领域,区块链技术不仅可以为支付、数字资产交易和智能合约保险等新兴金融商业模式提供底层技术支撑,同时,借助区块链构建低成本的生态信任体系,可大大地降低金融交易成本、提高金融运行效率。故近年来区块链的发展非常迅速[2]。

区块链一开始就应用于数字货币、金融领域,其安全性要求更高于其他技术。区块链的本质是一个去中心化的数据库,将数据信息采用分布式方法记录,在安全性方面,较传统实现了质的突破;但是区块链仍面临着大量的安全风险[3]。

区块链的安全风险包括网络与存储层安全风险、数据与算法层安全风险、共识与合约层安全风险和应用支撑层安全风险。其中,网络与存储层安全风险等与传统的安全风险类似;而共识与合约层安全风险是区块链独有的风险,该风险主要是针对共识和智能合约(Smart contract)这两项核心技术的攻击,包括“51%”攻击、女巫攻击、双花攻击和DDoS攻击,以及针对共识和智能合约逻辑漏洞实施的攻击等[4]。本文将对智能合约的安全风险进行分析,并提出智能合约的安全代码要求。

1 智能合约概述

区块链是用分布式数据库识别、传播和记载信息的智能化对等网络,也被称为价值互联网。区块链技术作为一种使数据库安全而不需要行政机构授信的解决方案首先被应用于比特币。自称日裔美国黑客的中本聪于2008年在《比特币白皮书》中提出“区块链”概念,并在2009年创立了比特币社会网络,开发出第一个区块,即“创世区块”[5]。

智能合约(Smart contract)的使用是第二代区块链技术与第一代的显著区别,智能合约这个术语在区块链之前就已出现,至少可以追溯到1995年,由多产的跨领域法律学者、受到广泛赞誉的密码学家尼克·萨博(Nick Szabo)所提出,他在发表于自己网站的几篇文章中提到了智能合约理念,定义如下:“一个智能合约是一套以数字形式定义的承诺(Promises),包括合约参与方可以在上面执行这些承诺的协议。”

智能合约理念几乎与互联网(World Wide Web)同时出现,从本质上讲,这些自动合约的工作原理类似于其他计算机程序的if-then语句,是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易,这些交易可追踪且不可逆转。当一个预先编好的条件被触发时,智能合约则执行相应的合同条款。

在计算机上进行智能合约的实际应用时,需要控制实物资产保证其有效地执行合约;同时要做到,执行合约条款时能获取到第三方审核的合约方信息,即需要解决信息传递与信任问题。

在无法建立信任关系的互联网上,区块链技术依靠密码学和巧妙的分布式算法,无需借助任何第三方中心机构的介入,用数学的方法使参与者达成共识,保证交易记录的存在性、合约的有效性和身份的不可抵赖性,由此解决了互联网上的信任和价值传递问题,为智能合约的广泛应用提供了绝佳的温床。第二代区块链开源项目——以太坊(Ethereum)即使用了智能合约,以太坊提供了图灵完备的脚本语言Solidity、Serpent与沙盒环境的以太坊虚拟机(EVM:Ethereum Virtual Machine),以供用户编写和运行智能合约。一个旨在推动区块链跨行业应用的开源项目——Hyperledge,由Linux基金会在2015年12月主导发起,它也支持智能合约,Hyperledger Fabric的智能合约被称为Chaincode,其选用Docker容器作为沙盒环境,Docker容器中带有一组经过签名的基础磁盘映像及Go与Java语言运行所需的SDK,以运行Go与Java语言编写的Chaincode。智能合约使很多不同类型的程序和操作得以自动化,最明显的体现之处在于支付环节及付款时的步骤操作。2016年底由智能合约联盟(SCA)与数字商务商会(CDC)联合发布的“Smart Contracts:12 Use Cases for Business & Beyond”(《智能合约:12种商业及其他使用案例》)白皮书介绍了数字身份、抵押、供应链和癌症研究等12项智能合约的商业使用案例,而目前智能合约已在金融、医疗等多个领域中得以实际应用[6]。

2 智能合约的安全风险

区块链的本质是一个去中心化的数据库。在运行的过程中,区块链将数据信息采用分布式方式记录,并且由所有的参与者共同记录。这些数据信息会被存储在所有的节点之中,不是像传统数据库一样,仅仅存在于唯一的中心化机构。因此,在安全性方面,较传统实现了质的突破。但区块链仍面临着大量的安全风险。

智能合约作为区块链的核心技术极大地提升了其应用前景。但是,由于区块链具有不可更改和不可撤销的特点,如果智能合约代码中存在漏洞,那么这些漏洞将为区块链的应用带来极大的安全隐患[7]。Ivica Nikolic等人通过对近100万份的智能合约进行研究,就发现其中相当多的智能合约存在漏洞。2018年,以太坊也曾因为智能合约安全问题而不得不出现了硬分叉,分叉的结果给以太坊社区带来了极大的争议和混乱,黑客盗取了价值约5 000万美元的以太币,当时为了挽回大部分人的损失,采取了硬分叉的策略,用新的长的链来代替被攻击的链,这样黑客的盗取就没有价值(参考上述举例)。但是,当时社区的部分支持者认为这是一个去中心化的社区,不应该由一个人来决定未来。所以愿意扛下被黑客攻击后的损失,以保证社区的去中心化。因此,社区之间产生了矛盾,就出现了硬分叉之后的两条链:以太经典(ETC)和现在的以太坊(ETH)。

智能合约是一段运行在区块链网络中的代码,它完成用户所赋予的业务逻辑。以以太坊体系的代币为例,其业务逻辑是代币发币和交易。以太坊在设计之初,将智能合约设计成了一旦部署就不能修改的模式。这种设计有可能是为了提高智能合约的可信性。

然而,只要是由人编写的程序,就一定会出现错误和缺陷。以太坊这种设计本身就违背了程序设计的一般规律,在智能合约出现漏洞的时候可能会造成无法弥补的损失。由于智能合约本质上是部署和运行在区块链上的程序,在没有标准的合约模板或编写规范的情况下,很难要求程序员都能够写出最佳实践的代码,一些逻辑不严谨的代码会造成智能合约的业务逻辑存在安全隐患[8]。目前智能合约出现的漏洞有20多种,其中主要的漏洞类型如下所述。

a)重入攻击

重入漏洞是最著名的以太坊智能合约漏洞,曾导致了以太坊的分叉(The DAO hack)。Solidity中的call.value()函数在被用来发送Ether的时候会消耗它接收到的所有gas,当调用call.value()函数发送Ether的操作发生在实际减少发送者账户的余额之前时,就会存在重入攻击的风险。

b)数值溢出

智能合约中的算数问题是指整数溢出和整数下溢。Solidity最多能处理256位的数字(2^256-1),最大数字增加1会溢出得到0。同样,当数字为无符号类型时,0减去1会下溢得到最大数字值。

整数溢出和下溢不是一种新类型的漏洞,但它们在智能合约中尤其危险。溢出情况会导致不正确的结果,特别是如果可能性未被预期,则可能会影响程序的可靠性和安全性。

c)访问控制

访问控制缺陷是所有的程序中都可能存在的安全风险,智能合约也同样会存在类似问题,著名的Parity Wallet智能合约就受到过该问题的影响。

返回值调用验证此问题多出现在和转币相关的智能合约中,故又被称作为静默失败发送或未经检查发送。在Solidity中存在transfer()、send()、call.value()等转币方法,都可以用于向某一地址发送Ether,其区别在于:transfer发送失败时会throw,并且进行状态回滚;只会传递2 300 gas供调用,防止重入攻击;send发送失败时会返回false;只会传递2 300 gas供调用,防止重入攻击;call.value发送失败时会返回false;传递所有的可用gas进行调用(可通过传入gas_value参数进行限制),不能有效地防止重入攻击。

如果在代码中没有检查以上send和call.value转币函数的返回值,合约会继续执行后面的代码,可能由于Ether发送失败而导致意外的结果。

d)错误使用随机数

智能合约中可能需要使用随机数,虽然Solidity提供的函数和变量可以访问智能合约审计报告明显难以预测的值,如block.number和block.timestamp,但是它们通常或者比看起来更公开,或者受到矿工的影响,即这些随机数在一定程度上是可预测的,所以恶意用户通常可以复制它并依靠其不可预知性来攻击该功能。

e)事务顺序依赖

由于矿工总是通过代表外部拥有地址(EOA)的代码来获取gas费用,因此用户可以指定更高的费用以便更快地开展交易。由于以太坊区块链是公开的,每个人都可以看到其他人未决交易的内容。这就意味着,如果某个用户提交了一个有价值的解决方案,恶意用户可以窃取该解决方案并以较高的费用复制其交易,以抢占原始的解决方案。

f)拒绝服务攻击

在以太坊的世界中,拒绝服务是致命的,遭受该类型攻击的智能合约可能永远无法恢复正常工作状态。导致智能合约拒绝服务的原因可能有很多种,包括在作为交易接收方时的恶意行为,人为增加计算功能所需的gas而导致gas耗尽,滥用访问控制访问智能合约的private组件,利用混淆和疏忽等。

g)逻辑设计缺陷

逻辑设计缺陷主要是指与业务设计相关的安全问题。

h)假充值漏洞

在代币合约的transfer函数对转账发起人(msg.sender)的余额检查用的是if判断方式,当balances[msg.sender]<value时进入else逻辑部分并returnfalse,最终没有抛出异常,我们认为仅if/else这种温和的判断方式在transfer这类敏感函数场景中是一种不严谨的编码方式。

i)增发代币漏洞

主要指初始化代币总量后,代币合约中是否存在可能使代币总量增加的函数。

j)冻结账户绕过

主要指代币合约中在转移代币时,是否存在未校验代币来源账户、发起账户和目标账户是否被冻结的操作。

在历次的安全事故中,因智能合约的漏洞引发了安全问题并占了较大的比重。根据智能合约检测结果,总结了主要的智能合约的安全漏洞类型,按照占比进行排序,如表1所示。

表1 智能合约安全漏洞分布

3 智能合约的安全代码要求

智能合约的操作对象大多为数字资产,其主要负责将业务逻辑以代码的形式实现、编译和部署,并按照既定的规则或者触发条件自动地执行,所以具有形式多样性和差异性,因此,与其他区块链安全问题相比,智能合约安全性问题往往会引发较大的经济损失且更加不可控[9]。

除少部分联盟链中支持智能合约在部署之后进行更新外,大多数智能合约通常具有发布即生效、不可更改等特点,同时,现阶段能够运行智能合约的区块链项目一般都处在早起阶段并且具有很强的实验性质,智能合约可能存在的安全隐患不断地被发现,相关项目面临的安全威胁也在不断地变化,因此,要应对智能合约的安全漏洞问题,需要在智能合约上线之前,对其进行全面深入的安全验证,从智能合约开发阶段就秉持一个全新的工程思维,尽可能地对安全隐患有所准备,本着一定的安全原则进行合约代码的编写,如“当智能合约出现错误时,停止合约”“管理账户的资金风险(包括限制转账速率、限制最大转账额度等)”等。致力于在智能合约开发阶段规避这些安全隐患,通过对智能合约已经出现的安全问题案例进行分析,以以太坊智能合约开发体系Solidity为例,整理出了智能合约开发中需要注意的重点内容,具体如下所述。

a)严格控制表征权限的变量和表示

访问控制权限是安全领域的防护重点之一。在智能合约中,针对权限,主要考虑是否为合约所有者、在合约内部还是外部、是否满足用户添加的权限要求。其实现依赖函数修饰符、判断语句等,通常会有变量或标识来表征。因此,必须对用于表征权限的变量和表示进行严格的控制,即这些敏感变量也应该通过函数修饰符进行权限控制,从而保证权限闭环,让攻击者无机可乘。具体地说,对于标识权限的变量,往往有对应的函数对其进行修改以实现一些预期功能。对这些函数,必须控制访问权限(通过函数修饰符或assert、if-else等实现)。

b)尽量避免外部调用

调用不受信任的外部合约可能会引发一系列意外的风险和错误,外部调用可能在其合约和它所依赖的其他合约内执行恶意代码。因此,在实现业务逻辑中时,多花一些时间和精力重复实现其他合约已经实现的业务逻辑,避免直接调用不受信任的外部合约可以直接规避相关的安全风险,与合约引入恶意代码攻击所造成的损失相比,重复“造轮子”很有必要。

c)仔细权衡“send()”“transfer()”和“call.value()”

在以太坊网络中进行转账Ether时,需要仔细权衡“someAddress.send()”“someAddress.transfer()”和“someAddress.call.value()()”之间的差别。由于触发机制的不同,与someAddress.call.value()()相 比,someAddress.send()和someAddress.transfer()能 保 证 可 重 入 安 全,someAddress.call.value()()将会发送指定数量的Ether并且触发对应代码的执行,而被调用的外部智能合约代码将享有所有剩余的gas,通过这种方式转账是很容易有可重入漏洞的,非常不安全。

d)使用assert()强制不变性

当断言条件不满足时果断地触发断言保护,比如不变的属性发生了变化。例如:代币在以太坊上的发行比例,在代币的发行合约里可以通过这种方式得到解决。断言保护经常需要和其他技术组合使用,比如:当断言被触发时先挂起合约然后升级。

e)正确使用assert()和require()

在Solidity0.4.10中assert()和require()被加入。require(condition)被用来验证用户的输入,如果条件不满足便会抛出异常,应当使用它验证所有用户的输入。assert(condition)在条件不满足时也会抛出异常,但是最好只用于固定变量:内部错误或你的智能合约陷入无效的状态。遵循这些范例,使用分析工具来验证永远不会执行这些无效操作码:意味着代码中不存在任何不变量,并且代码已经正式验证。

f)权衡Abstract合约和Interfaces

Interfaces和Abstract合约都是用来使智能合约能更好地被定制和重用。Interfaces是在Solidity0.4.11中被引入的,和Abstract合约很像但是不能定义方法只能申明。Interfaces存在一些限制比如不能够访问storage或者从其他Interfaces那继承,通常这些使Abstract合约更实用。尽管如此,Interfaces在实现智能合约之前的设计智能合约阶段仍然有很大的用处。另外,需要注意的是,如果一个智能合约从另一个Abstract合约继承而来,那么它必须实现所有Abstract合约内的申明并未实现的函数,否则它也会成为一个Abstract合约。

g)使Fallback函数尽量简单明了

Fallback函数在合约执行消息发送没有携带参数(或当没有匹配的函数可供调用)时将会被调用,一些攻击隐藏在Fallback函数中调用的外部合约,而Fallback函数逻辑不清导致递归时同样会造成gas的大量损失,因此尽量让Fallback函数简单明了,避免正常业务逻辑执行受阻而调用Fallback时引入安全风险。

h)明确标明函数和状态变量的可见性

明确标明函数和状态变量的可见性。函数可以声明为external,public,internal或private。分清楚它们之间的差异。例如:external可能已够用而不是使用public。对于状态变量,external是不可能的。明确标注可见性将使得更容易避免关于谁可以调用该函数或访问变量的错误假设。

i)保持智能合约的简洁

在智能合约的开发中,清晰明了往往比性能提升更重要,复杂的智能合约往往存在更多的安全隐患,因此,为了提升智能合约的安全性,首先应确保智能合约的逻辑简洁,坚持通过内部模块化和功能调用的编码风格实现合约逻辑,同时需尽量地使用被广泛验证过的合约和工具,如不要自己写一个随机数生成器。

4 结束语

智能合约作为以信息化方式传播、验证和执行合同的计算机协议,为区块链技术的发展和落地提供了更灵活的工具,然而,作为一种全新的技术应用,区块链智能合约面临的安全问题同样严峻。任何信息系统的安全维护都不是一劳永逸的,对智能合约的安全维护同样如此,通过对已出现的安全事故案例进行分析和总结,形成了一套智能合约安全开发规范,可以有效地指导开发人员在进行智能合约设计时规避安全风险;同时,鉴于越来越多的区块链项目支持合约在部署后进行修改和更新,实时跟踪新出现的智能合约漏洞,保持智能合约的动态检查同样重要。

猜你喜欢

以太代币调用
核电项目物项调用管理的应用研究
首次代币发行监管的行为经济学路径
LabWindows/CVI下基于ActiveX技术的Excel调用
车易链:做汽车业的“以太坊”
央行等七部门叫停各类代币发行融资
央行等七部门叫停各类代币发行融资
央行等七部门叫停各类代币发行融资
基于系统调用的恶意软件检测技术研究
A Study on the Contract Research Organization
百通推出入门级快速工业以太网络交换器系列