栏目分类
热点资讯
你的位置:ZTG中文网 > Vulcan Forged (PYR)中文网 > 智能合约安全漏洞检测研究进展

Vulcan Forged (PYR)中文网

智能合约安全漏洞检测研究进展

发布日期:2025-01-04 14:58    点击次数:181

区块链是以区块作为单位的链式结构, 区块由区块头和区块体2部分组成, 前者存储前一个区块的哈希值、当前区块时间戳和块难度等信息, 后者存储交易事务信息‎[1]. 区块链参与者提交的事务信息保存在彼此相连的区块中, 区块链使用不对称加密和分布式一致性算法保证区块链信息安全和账本一致性‎[2]. 区块链去中心化、可信任等特性使其在数字加密货币以及金融领域取得了广泛应用, 如比特币‎[3]及基于区块链的证劵交易平台Chain ()等. 智能合约是运行在区块链合约层的计算机程序‎[4], 由合约存储、余额和程序代码组成‎[5]. 区块链实现了去中心化存储, 智能合约在区块链的基础上实现了去中心化计算‎[1]. 智能合约能够管理区块链上的资金或数据, 其去中心化、公开透明、不可篡改以及可追溯等特性使智能合约在金融管理、物联网‎[6]以及医疗‎[7]等领域取得广泛应用. 目前, 已有很多区块链平台支持通过智能合约操作区块链, 如以太坊‎[8]、超级账本(https://www.hyperledger.org/projects)和EOS (https://eos.io/)等. 由于比特币平台仅支持通过脚本操作比特币, 不能实现复杂的业务逻辑, 通常不将比特币脚本视为智能合约. 智能合约能够实现较为复杂的控制逻辑, 实现多样化的业务需求. 然而, 智能合约的灵活性也为其带来了巨大安全风险. 一方面, 由于智能合约的编程体系发展历时较短, 开发者对区块链特性以及智能合约编程语言不够了解和熟悉, 导致其中存在较多漏洞. 另一方面, 智能合约常被用于管理区块链上的资产, 容易吸引攻击者关注, 大量攻击者试图通过潜在漏洞攻击智能合约, 以窃取他人财产. 已经发生过多起智能合约安全事件, 例如, 2016年6月, 攻击者利用TheDAO合约中的重入漏洞窃取了价值6000万美元的以太币(https://www.coindesk.com/learn/2016/06/25/underst anding-the-dao-attack/); 2017年7月, 攻击者利用Parity合约中的权限控制漏洞篡夺了合约权限, 窃取了价值3000万美元的以太币(https://hackingdistributed.com/2017/07/22/deep-dive-par ity-bug/); 2018年4月, 攻击者利用BEC合约中的整型溢出漏洞使BEC代币价值归零(https://blog.peckshield.com/2018/04/22/batchOverflow)等. 据SlowMist Hacked (https://hacked.slowmist.io/)统计, 截至2021年12月, 各区块链平台共计发生了550起安全事件, 损失金额达218亿美元. 此外, 与传统软件不同, 智能合约具有不可篡改性, 合约部署在区块链平台后难以通过补丁进行漏洞修复‎[9], 在部署智能合约前对其进行全面的安全检测极为重要. 智能合约中的安全漏洞指其中隐藏的错误, 这些错误可能会导致产生不正确或意外的结果, 或程序以非预期的方式执行. Bartoletti等人‎[10]对以太坊平台上的智能合约进行分类统计, 结果表明811个智能合约中存在373个金融类合约, 且759611次事务执行中624046次与金融交易相关, 即以太坊平台中46%的智能合约和82.2%事务执行与数字资产的存储或转移相关. 因此智能合约安全漏洞与通常意义下的软件安全漏洞不同, 前者相较于后者能够更直接地处理数字资产, 智能合约中的安全漏洞若被不法分子或攻击者利用, 将直接对智能合约中管理的数字资产造成威胁. 因此, 智能合约的安全漏洞检测得到了研究人员的广泛关注. 例如, Luu等人‎[11]通过符号执行检测Solidity智能合约中重入、事务顺序依赖、时间戳依赖和未检测CALL返回值4种安全漏洞; Jiang等人‎[12]利用模糊测试技术对Solidity智能合约中的贪心、无gas发送和时间戳依赖等7种安全漏洞进行检测等. 目前, 已有一些文献对智能合约安全漏洞检测问题进行了调研. 付梦琳等人‎[13]对智能合约安全漏洞挖掘技术进行了调研, 总结了10种漏洞类型并对6种检测方法进行了比较分析; 倪远东等人‎[14]对漏洞分类进行了讨论, 将已有漏洞类型划分为3个层面, 即高级语言、虚拟机和区块链, 并对15种主要安全漏洞进行了分析, 总结了3种智能合约安全防御策略; 郑忠斌等人‎[15]对14种安全漏洞进行了分析, 并提供了防范策略; 钱鹏等人‎[16]介绍了15种典型安全漏洞及5个典型安全事件, 并分5类检测技术类别对25种检测方法进行了描述, 总结了各类别检测方法的优势与不足; 涂良琼等人‎[17]从是否需要运行合约程序的角度将检测工具分为静态检测工具和动态检测工具, 并比较和分析了各检测工具的漏洞检测能力. 胡甜媛等人‎[18]将智能合约的安全问题总结为合约安全和隐私安全两个方面, 并从设计、实现、测试、部署与运维等角度对智能合约安全问题的研究现状进行梳理, 提出未来的研究工作应围绕智能合约的全生命周期中每个阶段的安全问题进一步推进. 与已有的研究综述相比, 我们的工作存在如下不同. 一方面, 本文补充了重要的研究进展, 并提出智能合约安全漏洞分类框架. 智能合约安全漏洞检测工作的文献数量每年呈增长趋势. 在最新智能合约安全漏洞检测综述, 即文献‎[16]于2021年5月发表后, 在IEEE Xplore上有23篇新发表文献, 其中包括权威国际期刊和会议TSE、ISSTA等. 本文在已有综述的基础上补充了如DefectChecker (TSE)‎[19]、sFuzz (ICSE)‎[20]、SmartTest (S&P)‎[21]和Harvey (FSE)‎[22]等检测方法的比较和分析. 同时, 本文在已知的15种漏洞模式基础上增加了26种漏洞模式, 更加全面、详细地描述了目前已知的漏洞模式, 并归纳出智能合约安全漏洞分类框架, 该框架能够适配目前已知的智能合约安全漏洞, 且为未来的安全漏洞分类提供参考. 另一方面, 本文将研究进展的分析范围扩展到整个安全漏洞检测流程, 相较于已有的研究综述更加全面. 现有综述主要从检测技术角度出发, 描述了已知漏洞模式并对各检测技术进行分析和比较. 而漏洞模式分析是安全检测步骤的前提, 数据集和评价指标是验证方法有效性的数据支撑和评估手段. 本文进一步将研究进展的分析范围扩展到整个安全漏洞检测流程, 补充了漏洞信息收集、漏洞模式识别、数据集获取和评价指标选择等关键步骤的描述和分析, 提出了更为完整的智能合约安全漏洞检测研究框架, 希望能够为未来的研究工作提供帮助. 综上所述, 为了能够系统全面地了解智能合约安全检测领域的科研进展, 我们将smart contract (智能合约)组合bug (错误)、defect (缺陷)、vulnerability (漏洞)、analysis (分析)和detect (检测)等作为关键字, 在IEEE Xplore、ACM Digital Library、Springer Link Online Library和CNKI等在线文献数据库中搜索截至2023年5月与智能合约安全漏洞分析及检测相关的文献, 并特别检索了TSE、ASE、ICSE以及CCS等软件工程和网络与信息安全的权威国际期刊与会议. 同时, 为了避免遗漏, 我们利用雪球文献搜索法‎[23], 对上述论文所引用的参考文献进行人工检查, 进一步弥补遗漏. 此外, 我们详细分析和整理了已有调研工作中的文献, 补充到我们的文献统计中. 最终, 我们检索到与智能合约安全漏洞检测相关的论文281篇. 图1为根据发表年份对上述文献进行统计的柱状图, 从图中可以看出与安全漏洞检测相关的文献逐年递增. 其中目前检索到的2023年文献较少是由于部分2023年度的文献尚未被数据库收录. 图1所示的趋势表明针对智能合约安全问题的研究持续升温. 我们将论文的统计和收集结果公布在GitHub (https://github.com/yagol2020/ SmartContract-Security-Papers)中, 以供未来研究人员参考. 图 1 智能合约安全漏洞检测论文发表情况统计 通过分析论文作者和研究单位, 我们发现目前在智能合约安全漏洞检测领域上最为活跃的研究单位是莫纳什大学/华为公司夏鑫研究团队, 其研究成果发表在ASE、TSE等权威国际会议或期刊, 涉及漏洞发现、分类‎[24]与检测‎[19,25]等步骤, 形成了较为完整的智能合约安全漏洞检测体系. 此外, 高丽大学Hakjoo Oh研究团队‎[21,26]、北京航空航天大学姜博研究团队‎[12,27]也在智能合约安全漏洞检测领域取得了较多的成果. 其他研究单位中, 电子科技大学、北京大学、北京交通大学等高校的研究人员也屡有高质量研究成果发表. 本文从281篇文献中选取TSE、ASE及ICSE等软件工程领域国际权威期刊或会议33篇, CCS、USENIX Security等网络与信息安全领域权威期刊或会议19篇, IJCAI、IJCNN等人工智能领域权威会议2篇, TKDE等数据挖掘领域权威期刊1篇, 以及21篇以上期刊或会议论文中提及的参考文献, 共计筛选出76篇高质量论文作为分析对象. 筛选出论文所属的领域、CCF等级、发表源简称和论文发表数量如表1所示. 表 1 本文筛选的76篇分析对象的论文发表情况 本文的主要贡献总结如下. (1) 梳理并提出了目前智能合约安全漏洞检测方法的研究框架, 将其划分为3个阶段, 即漏洞发现与识别、漏洞分析与检测以及数据集与方法评估, 并依次详细介绍和归纳各阶段的通用技术. 本文提出的系统性研究框架可为研究人员提供帮助. (2) 在已有漏洞分类标准的基础上, 进一步将检测方法中出现的漏洞模式进行总结, 提出智能合约安全漏洞分类框架, 将已知的漏洞模式根据其基础特征归纳为异常处理、权限控制、区块信息依赖等13类漏洞类型. 此外, 我们整理了41种漏洞模式, 相对于文献‎[16]扩展了26种. (3) 从检测方法分类的角度出发, 将检测方法划分为基于符号执行、模糊测试、机器学习、形式化验证和静态分析5种类型, 分析各种方法的优势和不足, 并展望了未来可研究的方向. (4) 整理了智能合约安全漏洞检测领域常用的公开数据集以及评价指标, 为未来对智能合约安全漏洞检测方法的研究提供验证数据集支持以及评价指标参考. 本文第1节概要介绍目前安全漏洞检测的研究框架. 第2节描述智能合约安全漏洞信息收集的基本流程, 整理归纳了已知安全漏洞模式, 并对漏洞检测情况进行分析. 第3节详细介绍目前已有的检测方法, 划分为5种类型, 并逐一分析各类检测方法的优势与局限性. 第4节统计智能合约安全漏洞检测领域的数据集, 并介绍常用的评价指标. 最后在第5节总结全文, 分析现有的检测方法, 并展望未来的研究方向. 1 研究框架 本文将目前智能合约安全漏洞检测方法的研究框架总结如图2所示. 现有的检测方法根据其检测流程可分3个阶段: 漏洞发现与识别、漏洞分析与检测、数据集与方法评估. 图 2 智能合约安全漏洞检测研究框架 漏洞发现与识别是进行智能合约安全漏洞检测的前提. 首先需要了解漏洞特征, 并针对漏洞特征设计可用于检测漏洞的模式, 才能有效发现智能合约中存在的漏洞. 目前已有SWC Registry (https://swcregistry.io/)等平台发布和更新漏洞信息. 此外, 部分研究者收集StackOverflow等Q&A平台中开发者关于智能合约的讨论信息, 经过整理、分类和归纳后给出常见的漏洞类型. 如Chen等人‎[24]收集了StackExchange平台(https://ethereum.stackexchange.com/)上17128条讨论数据, 通过关键词筛选和人工检查后, 总结出了16种漏洞模式. 在本文的第3节将介绍漏洞信息收集的基本流程, 并对已知的漏洞模式整理分类. 漏洞分析与检测是检测方法的核心步骤. Ren等人‎[28]对智能合约测试工具进行了实证研究, 将现有的测试工具分为4种类型, 即符号执行、动态模糊测试、深度学习和静态分析. 我们参考该文中的检测方法分类标准, 并补充了基于形式化验证的检测方法. 在本文的第4节将逐一介绍各类检测方法的已有工作及特点. 数据集与评价指标可用于验证方法的有效性. 数据集根据有无漏洞标签可分为两种类型. 无漏洞标签的数据可从区块链浏览器(如EtherScan (https://etherscan.io/)等)获取. 有漏洞标签的数据集可从两种途径获得, 一种是经过人工审查、分析和标注的数据集, 另一种是经过检测工具检测后标注的数据集, 以上两种途径标注的数据集通常可通过开源平台(如GitHub等)获取. 根据评价的阶段不同, 评估检测方法有效性的评价指标可分为关注检测过程和关注检测结果2类. 在本文的第5节将详细描述有关数据集和评价指标的有关内容. 2 智能合约安全漏洞发现与识别 漏洞发现与识别是安全漏洞检测的准备步骤. 在对智能合约进行检测前, 首先需要收集漏洞信息, 了解漏洞特征, 并发掘隐含的漏洞模式, 然后才能针对漏洞模式设计有效的检测方法. 本节将描述在安全漏洞检测工作中研究者收集漏洞信息的方法步骤, 以及目前已知的漏洞模式. 2.1 安全漏洞信息收集 漏洞信息与软件测试中测试报告相似, 可通过自然语言或代码示例描述触发漏洞的方法或操作, 以及导致漏洞产生的原理. 在智能合约安全检测领域, 虽然没有系统地对漏洞信息收集这一步骤进行研究, 但已有部分研究者通过文献阅读、问卷调查或专家评审等方式收集漏洞信息, 并对其加以分析, 为识别漏洞模式提供信息支持. 本文将安全漏洞信息收集的基本流程总结如图3所示. 首先, 通过开源仓库、博客、Q&A或讨论平台等互联网渠道收集原始漏洞数据, 从中提取疑似漏洞的信息; 然后, 通过专家分析或问卷调查等途径对可疑漏洞进行验证, 以获得更为准确的漏洞信息. 图 3 智能合约安全漏洞信息收集流程图 Atzei等人‎[29]通过分析与以太坊相关的学术论文、互联网博客以及论坛, 并结合作者在智能合约上的编程经验, 将12种漏洞根据其被触发的环境划分为3个类别, 即Solidity层、EVM (Ethereum virtual machine, 以太坊虚拟机)层和区块链层, 并通过漏洞原理、漏洞示例、漏洞相关语法知识对漏洞进行了详细描述. 此外, 对TheDAO、KotET及Multi-player游戏等6个事件或典型合约进行了分析, 阐述了其中隐藏的漏洞. Chen等人‎[24]在StackExchange平台中收集与智能合约相关的讨论信息, 如开发人员遇到的问题(issues)、分享的编程经验及解决方案等. 在收集到的17128条讨论信息中, 进一步通过关键字筛选和人工筛选的方法获得393条与智能合约缺陷相关的讨论信息. 在此基础上, 借助卡片分类法人工分析以上信息, 最终总结出16种漏洞, 并根据其带来影响的不同分为5种类型, 即影响安全、性能、可用性、可维护性和可复用性的漏洞. 此外, Chen等人‎[24]还通过问卷调查的方式验证以上总结出的漏洞描述和漏洞分类是否合理, 结果表明大部分智能合约开发者及科研人员认同该分类结果. Chen‎[30]观察到Solidity智能合约能够通过self-destruct指令自我销毁, 部分开发者利用这种方式销毁存在漏洞的智能合约, 经过漏洞修复后部署新合约. 基于以上观察, Chen从EtherScan中收集了54739份源代码, 其中包含756份被销毁合约, 将54739份源代码根据创建者聚类分组, 组内按照时间顺序进行排序, 将销毁合约和仍运行的合约组合为一对合约, 利用AST解析源代码, 通过替换变量名称等方式删除与语义无关的信息. 然后通过FastText‎[31]转换为向量并计算相似度, 当相似度高于0.6时则视为这一对合约存在更新关系. 最后根据更新关系分析销毁合约中存在的安全漏洞, 并利用卡片分类方法整理出了权限控制、未检测CALL返回值等4种漏洞. 此外, Wohrer等人‎[32]通过审阅Solidity开发文档、互联网博客、论坛以及部分真实智能合约, 从中总结出6种漏洞及对应的安全设计模式, 按照安全设计模式编程将避免产生相关漏洞. Delmolino等人‎[33]收集高校学生使用Serpent语言编写的智能合约, 分析并总结出6种Serpent智能合约漏洞. Durieux等人‎[34]通过not-so-smart-contracts仓库、Positive.com博客等途径收集漏洞信息并构建了智能合约漏洞数据集(https://github.com/smartbugs/smartbugs), 利用该数据集对9种检测工具的性能进行比较与分析. Dias等人‎[35]从Sigma Prime、DASP以及SWC Registry等数据源收集漏洞信息, 从漏洞信息中提取并定义出智能合约安全漏洞分类框架, 将已知漏洞分为智能合约特有漏洞和一般漏洞, 然后构建漏洞数据集(https://zenodo.org/record/5512155)对3种检测工具的性能进行比较与分析. 除以上研究中提到漏洞信息收集方法外, 还可通过智能合约漏洞信息公开网站获取漏洞信息, 如SWC Registry和SlowMist Hacked等. 这类网站提供了实时更新的漏洞信息, 如SWC Registry提供漏洞描述、漏洞示例以及测试用例等信息; SlowMist Hacked提供被攻击对象、攻击事件描述及攻击方法等信息. 结合以上描述, 本文将目前智能合约安全漏洞检测相关文献中出现的漏洞信息数据源总结为表2. 其中, 文献指基于该数据源提出漏洞定义或分类的文献序号. 表 2 智能合约安全漏洞信息数据源 2.2 安全漏洞模式分类 漏洞信息可以帮助研究者分析漏洞模式, 从而设计有效的检测方法. 本节将描述在智能合约安全漏洞检测领域研究工作中发现的典型漏洞. 本文参考文献‎[16]和文献‎[29]中的漏洞分类方法, 将层次扩展为高级语言、虚拟机和区块链3层. 其中, 高级语言层漏洞指因编程语言的语法特性而产生的漏洞, 虚拟机层漏洞指因虚拟机的特性或限制而产生的漏洞, 区块链层漏洞指由于区块链的特性而产生的漏洞. 进一步地, 本文提出了智能合约安全漏洞分类框架, 将已知漏洞根据其基础特征归纳为变量计算与存储、异常处理和未知函数调用等13种漏洞类型, 并在每类漏洞的描述中总结了异同点. 其中, 因缺少与重入漏洞相似的漏洞, 故将其单独分类. 最终, 我们整理出41种漏洞模式, 并将漏洞层次、类型、名称和可检测到该类漏洞的文献整理如表3所示. 需要注意的是, 部分漏洞暂时无法被现有的工具或方法检测, 例如数据存储位置冲突、缺少提示等漏洞暂无对应检测方法, 对于该部分漏洞, 我们给出了提出该漏洞定义的文献序号, 以供读者查看. 表 3 智能合约安全漏洞分类框架 2.2.1 高级语言层安全漏洞 高级语言指能用于编写智能合约的高级编程语言, 如Solidity (https://soliditylang.org/)、Vyper (https://github.com/vyperlang/vyper)等. 这个层次的漏洞与语言特性有关, 例如回调机制导致重入漏洞, 以及由于send、transfer和delegatecall发生错误时处理方式不同而导致的异常处理漏洞等. 本节将介绍高级语言层的漏洞. (1) 重入类漏洞 重入类漏洞指在合约未完成当前调用的状态下, 被恶意地再次执行调用函数, 从而导致恶性递归调用. 以太坊中, 当合约A向合约B转移以太币时, 由于Solidity语法机制将自动触发合约B的回调函数. 这个中间状态下, 合约B可以通过回调函数再次调用合约A中的函数, 由于合约A中的变量尚未被修改, 因此条件仍然满足, 致使再次转移以太币到B合约中. 重入漏洞是TheDAO事件中的关键漏洞, 不仅导致了价值6000万美元的以太币被窃取, 还致使以太坊产生了硬分支(https://blog.ethereum.org/2016/07/20/hard-fork-completed/). (2) 贪心类漏洞 贪心类漏洞指合约能够接收以太币, 但无法将自身的以太币转移出去. 通常含有贪心漏洞的合约缺少转移以太币的指令, 或者转移指令无法到达. 例如转移的条件过于严格(即严格余额检查漏洞), 或者依赖于其他合约, 通过delegatecall转移以太币, 但其他合约存在缺陷被攻击导致无法使用. 贪心漏洞是Parity合约存在的漏洞之一, 在Parity合约依赖的多签名钱包合约被恶意销毁后, Parity合约中的以太币无法转移, 导致价值3亿美元的以太币被冻结在合约中(https://www.parity.io/blog/a-postmortem-on-the-par ity-multi-sig-library-self-destruct/). 严格余额检查是导致贪心漏洞的常见原因, 该漏洞指判断条件根据合约余额是否与规定数量相等决定是否执行代码, 而攻击者可绕过以太币发送数量的检查条件强制增加以太币(如通过suicide指令), 导致余额检查条件无法满足, 无法执行发送以太币的代码, 产生含有贪心漏洞的合约. (3) 变量操作类漏洞 变量操作类漏洞指对变量操作不当而产生的错误. 数值计算相关漏洞是变量操作类漏洞中的一种类型, 指对数值类型变量操作不当而导致计算结果与预期不符, 进而产生非预期行为. 据CVE统计(), 截止到2019年3月31日, 以太坊智能合约中95.7%的漏洞与数值计算漏洞相关‎[26]. 变量操作类漏洞包括6种, 即整型溢出、除零/模零、截断错误、算术符号、变量类型推断错误和数据存储位置冲突漏洞. 1) 整型溢出漏洞 整型溢出指数值计算的结果超出了变量可表示的范围, 导致计算结果与预期不符. Solidity为整型开辟固定长度的存储空间, 若将超过最大存储范围的计算结果存储在较小的类型变量中, 将会导致整型溢出. 整型溢出是智能合约中常出现的错误, 例如BeautyChain合约的开发者错误地将乘法的计算结果存放到较小范围的变量中, 使得攻击者绕过了数值大小检验, 生成了大量代币, 致使该代币的价值大幅度下降, 对持有者的经济利益造成了损失(https://tinyurl.com/yd78gpyt). 2) 除零/模零漏洞 除零或模零属于软件中的常见错误, 即在数值计算中位于分母的变量在一定条件下为0, 导致运算错误. 3) 截断漏洞 截断漏洞指将高范围的整型转换为低范围的整型时, 部分数值丢失导致的数值错误. 例如用uint32存储uint类型的msg.value, 由于Solidity中uint默认为uint256, 因此当msg.value大于232 – 1时, 会触发截断漏洞, 使得存储的值小于msg.value, 导致合约错误地记录以太币数量. 4) 算术符号漏洞 算术符号漏洞指将带符号整型转换为不带符号整型时, 生成与预期不符的数值. 5) 变量类型推断错误漏洞 变量类型推断漏洞指在编码过程中不指定变量类型, 而是由编译器根据变量数值进行选择, 编译器将根据变量数值大小选择内存占用最小的变量, 因此在数值超过变量类型最大表示范围时将会出现逻辑错误. 例如“var i = 0”语句将导致i的变量类型为uint8, 而uint8的最大值为255, 如果将i作为循环控制变量遍历长度超过255的数组, 则将导致该循环遍历无法终止. 6) 数据存储位置冲突漏洞 数据存储位置冲突指在函数中创建struct、mapping或arrays类型变量, 导致变量指针指向错误位置, 使合约对变量的操作发生错误. 在传统语言(如C或Java)中, 函数内创建的变量将动态分配内存(memory)空间, 而在Solidity中struct、mapping和arrays这3种类型变量存储在存储(storage)空间中. 由于storage不会动态分配, 因此在函数中创建这3种类型变量时, 若未指向已分配的storage变量, 则默认指向0号storage指针(https://docs.soliditylang.org/en/v0.8.10/types.ht ml#data-location-and-assignment-behaviour), 将使该变量的指针引用错误, 导致错误的变量操作. (4) 异常处理类漏洞 异常处理类漏洞指因程序没有正确分析函数的返回值或异常而导致的逻辑错误. Solidity语言中, 不同的函数拥有不同的返回值或异常类型, 如send、call和delegatecall函数出现错误时返回false, 而transfer出现错误时抛出异常. 当出现异常时, 以太坊会将事务回滚, 状态变量恢复到调用前的数值. 由于send、call和delegatecall不会抛出异常, 因此如果程序没有考虑函数的返回值, 在调用失败时不进行处理而继续执行代码, 则可能导致代码逻辑错误. 此外, 调用过程中若gas数量不足, 将抛出out-of-gas异常, 若未进行处理将导致逻辑错误, 甚至合约无法运行. 异常处理类漏洞包括4种, 即未检测CALL返回值、无gas发送、拒绝服务和无限循环漏洞. 1) 未检测CALL返回值漏洞 未检测CALL返回值指程序对于CALL操作码的返回值缺少检查. CALL是send、transfer、call和delegatecall函数的EVM操作码, 如果开发者没有检测CALL的返回值, 可能导致代码逻辑错误. 此外, 对于嵌套调用链, 如果存在至少一个调用是通过call或delegatecall进行的, 则异常传递和交易回滚将会在该函数调用处停止并返回false, 然后继续执行剩余代码, 导致调用者无法获知整个嵌套调用链中的异常. 此漏洞是KotET游戏中存在的漏洞, 该游戏曾由于没有正确处理send函数的返回值, 导致部分King的利益受损(https://www.kingoftheether.com/postmortem.html). 2) 无gas发送漏洞 无gas发送指没有携带适量的gas向外部合约转移以太币, 触发了out-of-gas异常且没有正确处理该异常. 以太坊账户分为两种, 即合约账户以及外部地址‎[1]. 合约账户运行智能合约, 由合约代码控制; 外部账户能够部署或调用智能合约, 由用户私钥控制. 向外部账户发送以太币仅需2 300 gas, 而向合约账户发送以太币需要超过2 300 gas, 因此使用send或transfer函数向合约账户发送以太币会导致out-of-gas异常. 3) 拒绝服务漏洞 拒绝服务指循环中抛出异常而导致整个循环被回滚. 循环体中若执行transfer或其他能够抛出异常的函数, 则抛出异常后将导致整个循环所修改的状态被回滚, 最终导致该循环体甚至合约无法正常执行. 4) 无限循环漏洞 无限循环漏洞指函数中的递归或循环没有终止条件, 或终止条件无法满足, 导致gas被不断消耗, 最终导致合约无法正常运行. 此外, 由于Solidity智能合约中的fallback机制, 因此可能导致合约间调用的无限循环. 例如函数a调用外部合约C的函数b时, 由于函数名称或参数错误导致自动调用了合约C的fallback函数, 而fallback函数中进一步调用了函数a, 则由此产生了函数a和fallback函数之间的无限循环. (5) 未知函数调用类漏洞 未知函数调用指在合约调用外部函数时, 可能会调用存在陷阱的函数或存在漏洞的函数, 致使调用合约的代码逻辑出现错误, 甚至会窃取调用合约持有的资产. 根据调用方式的不同, 可将该漏洞分为3种, 即调用未知回调函数、delegatecall函数调用未知函数以及调用未知library引起的漏洞. 1) 调用未知回调函数漏洞 当通过send或transfer函数转移以太币, 或通过call函数调用外部函数且外部函数签名或类型无法匹配时, 将自动调用回调函数, 攻击者可以通过回调函数再次调用函数导致重入漏洞, 或编写gas消耗较多的回调函数导致out-of-gas异常, 引发非预期行为. 2) delegatecall函数调用未知函数漏洞 调用delegatecall函数时, 代码运行的上下文环境处于调用者合约中, 此时this指针指向调用者自身, 攻击者如果在delegatecall调用的外部函数中编写send或suicide指令, 可能导致合约资产被恶意转移或合约被销毁. 3) 调用未知library漏洞 Solidity允许使用using关键字将其他合约作为library, 从而将其他合约的函数作为库函数使用, 如果没有关注library的安全性, 可能导致将其他合约中的漏洞引入到调用合约. (6) 权限控制类漏洞 权限控制类漏洞指因程序设计错误而导致攻击者获得合约权限, 致使合约被恶意执行敏感操作. 敏感操作指涉及余额变动或合约控制的操作, 如转移以太币、调用外部函数、修改状态变量或合约销毁等. 开发者可通过修改状态变量来设置合约权限, 当调用者具备相应权限时才能执行敏感操作. 然而, 部分合约中由于缺少设置权限的条件, 或错误地将设置权限的函数可见性声明为public, 甚至没有针对敏感操作进行权限控制, 导致合约存在安全问题. 权限控制类漏洞可分为5种, 即任意发送以太币、自杀、函数可见性、任意修改状态变量和构造函数名称漏洞. 1) 任意发送以太币漏洞 以太币的转移属于智能合约中的敏感操作, 如果任何人均可执行send或transfer函数, 并且能够控制转移地址参数, 则合约中的以太币可以被发送给任何人. 2) 自杀漏洞 智能合约的自杀也属于智能合约中的敏感操作, 与任意发送以太币漏洞相似, 如果任何人均可执行suicide指令, 则可能导致合约被恶意销毁. 3) 函数可见性漏洞 Solidity默认的函数可见性为public, 而合约中的关键函数应当声明为private. 4) 任意修改状态变量漏洞 任意修改状态变量漏洞指修改状态变量的函数能够被攻击者恶意执行, 从而修改了状态变量导致合约运行出现错误. 智能合约中的状态变量通常记录较为重要的信息, 例如合约的拥有者、参与者或某个函数触发的条件等等. 合约的权限可通过设置状态变量实现, 然后通过require函数或if语句实现对执行敏感操作的调用者的身份验证. 然而若状态变量可被外界任意修改, 将会导致合约的权限被篡夺, 从而引发漏洞. 5) 构造函数名称漏洞 与传统软件相似, 在智能合约中构造函数指创建合约时执行的函数, 构造函数在合约创建完成后不再被执行, 通常用于初始化权限设置, 如设置合约拥有者等. 构造函数应与合约名称相同, 如果不同且未指定可见性则可见性默认为public, 即任何人均可调用该函数获得合约的权限, 从而导致权限控制漏洞. 权限控制漏洞是典型的智能合约漏洞, 该漏洞曾引发多起安全事件, 如Parity事件中, 攻击者通过调用没有被权限保护的初始化函数, 窃取多签名钱包的合约权限, 并调用存在suicide指令的函数将多签名钱包销毁, 致使Parity合约中的以太币被冻结(https://www.theregister.com/2017/11/10/parity_280 m_ethereum_wallet_lockdown_hack); Rubixi合约存在错误的构造函数名称漏洞, 错误地将构造函数的名字命名为“DynamicPyramid”而不是“Rubixi”, 导致任何人均可获得合约权限, 非法获取其中的资产(https://blog.ethereum.org/2016/06/19/thinking-smart-contract-security/). (7) 高gas消耗类漏洞 高gas消耗类漏洞指合约代码中使用不恰当的变量类型或代码模式, 导致产生可避免的gas消耗, 对合约拥有者和调用者造成经济损失. 该类漏洞可分为3种, 即不恰当函数或变量类型、高gas循环以及高gas代码模式漏洞. 1) 不恰当函数或变量类型漏洞 不恰当函数或变量类型指因使用消耗较多gas的函数类型或变量类型而造成的不必要消耗. 为避免此类漏洞, 对于不会被内部调用的函数, 应将其声明为external而不是public‎[64], 应使用bytes代替byte等. 2) 高gas循环漏洞 在循环体中使用call函数时, 由于call携带的gas数量较多, 如果不规定循环体的大小, 将导致gas被大量消耗, 甚至造成out-of-gas异常. 3) 高gas代码模式漏洞 不恰当的代码模式可能导致消耗不必要的gas, 例如应将多个JUMPDEST可合并为同一个JUMPDEST从而减少gas消耗‎[63]. (8) 交互类漏洞 交互类漏洞指代码中缺少对输入、执行过程以及输出的交互或干预. 如缺少对参数的验证、缺少将执行状况反馈给调用者或缺少返回值等. 交互类漏洞可分为4种, 即无效参数、缺少提示、缺少返回值和缺少中断漏洞. 1) 无效参数漏洞 无效参数漏洞指缺少对参数有效性的验证, 如数值范围, 或者参数应该在某一个列表中(例如游戏的参与者列表)等. 2) 缺少提示漏洞 缺少提示指合约在执行过程中缺少给予调用者的反馈提示. ABI (application binary interface, 应用二进制接口)是以太坊框架上的合约交互接口, 能够实现智能合约之间, 或智能合约与区块链外程序, 如Dapp (decentralized application, 去中心化应用)之间的交互. 调用过程中ABI仅告知函数的输入值和输出值类型, 而不告知函数是否调用成功. 在此情况下, 应通过event将调用信息反馈给调用者, 以减少不必要的错误或者gas消耗. 3) 缺少返回值漏洞 缺少返回值指合约没有规定返回值, 导致调用者获得的返回值信息与预期不符. 若函数未规定返回值的具体数值, EVM会为这种函数添加默认数值, 但该数值的含义可能与函数本身返回值的含义不符, 从而导致错误. 4) 缺少中断漏洞 缺少中断指合约没有能够中途停止或销毁的手段, 无法在发现漏洞时中断合约执行从而避免更多损失. 中断函数指能够结束合约生命周期, 停止合约运行的函数. 合约应该拥有至少一个中断函数, 如一个在限定条件下能够调用suicide指令的函数, 从而能够在合约受到攻击, 或者发现代码逻辑出现错误时及时停止运行, 减少经济损失‎[65]. (9) 影响鲁棒性类漏洞 影响鲁棒性指对智能合约的生命周期或健壮性产生影响的编码习惯. 该类漏洞可分为5种, 即没有正确实现ERC-20接口、使用废弃接口、没有标明编译器版本、无用参数和地址硬编码漏洞. 1) 没有正确实现ERC-20接口漏洞 ERC-20接口是以太坊的技术标准(https://github.com/ethereum/EIPs/blob/master/EIPS/ei p-20.md), 该接口定义了9种函数以及两种event, 实现了ERC-20接口的合约即满足ERC-20标准, 便于和其他符合标准的合约进行交互. 没有正确实现ERC-20接口指合约没有按照ERC-20的标准实现接口. ERC-20规定了函数的参数类型和返回值的含义, 如果没有正确实现接口的函数或事件, 如函数返回值的含义与标准不同, 则可能产生安全问题. 2) 使用废弃接口漏洞 使用废弃接口指在合约代码中使用旧版本语言支持的接口, 而该接口将在未来版本中被废弃, 使用可能被废弃的接口会降低合约的可复用性. Solidity版本变更较快, 在其官方文档(https://docs.soliditylang.org/)中会提示开发者部分接口在未来版本中将被废弃. 尽管该API在当前版本仍可使用, 但使用废弃API的智能合约代码将会降低其可复用性, 从而间接增加开发成本. 3) 没有标明编译器版本漏洞 没有标明编译器版本指在合约代码中没有通过pragma指令指定编译器版本, 而不同编译器接口的实现可能存在不同, 由此可能会导致API调用与预期不符, 甚至编译失败. 4) 无用参数漏洞 合约中多余的参数或者变量将影响代码的可读性. 5) 地址硬编码漏洞 地址硬编码指在部署合约时将账户地址编码在合约代码中, 在该地址处于危险状态时无法修改合约代码中的地址, 对合约的正常运行造成影响. 2.2.2 虚拟机层安全漏洞 虚拟机指运行智能合约的运行环境, 如以太坊平台的智能合约经过编译后运行在EVM上, EOS平台的智能合约经过编译后运行在WebAssembly虚拟机(https://webassembly.org/)上. 处于这个层次的漏洞通常与虚拟机实现有关, 例如EVM对转移以太币的目标地址不会进行检测, 如果目标地址尚未存在账户则创建一个独立账户, 导致丢失以太币漏洞, 或EVM堆栈大小限制调用次数小于1024, 调用链的深度超过堆栈大小会导致堆栈溢出等. (1) 以太坊地址类漏洞 以太坊地址类漏洞指因以太坊地址数值错误而导致的地址无法识别或识别错误问题. 以太坊中通过地址识别账户, 如果地址输入错误将无法找到指定账户, 导致转移的金钱被退回甚至丢失. 常见的以太坊地址类漏洞可分为2种, 即短地址漏洞和地址错误漏洞. 1) 短地址漏洞 短地址漏洞指输入的以太坊地址少于20字节, 虚拟机读取到非地址数据导致数据解析错误. 以太坊虚拟机在解析时读取20字节的数据作为地址, 因此攻击者可以输入少于20字节的数据, 使得虚拟机读取非地址字段的数据, 如记录以太币数量的数据, 致使以太币数量被错误解析. 2) 地址错误漏洞 地址错误漏洞指输入的以太坊地址错误或不存在, 虚拟机将自动创建账户, 由于该账户没有代码信息和合约拥有者, 将导致账户中的以太币无法取出, 进而引发贪心漏洞. (2) 调用链类漏洞 调用链类漏洞指因合约错误使用调用链信息, 或因触发虚拟机对调用链的限制而产生的错误. 该类漏洞可分为两种, 即tx.origin漏洞和调用堆栈溢出漏洞. 1) tx.origin漏洞 tx.origin漏洞指将tx.origin属性作为执行敏感操作的判断条件, 而该属性可被攻击者伪造, 从而绕过检查条件恶意执行敏感操作. tx.origin是Solidity智能合约中的全局变量, 能够获得事务链上首个发起调用的调用者地址, 但由于合约的函数可以被外部调用, 因此攻击者在外部调用合约时, 可以指定事务的发动者, 从而通过权限验证, 使攻击者窃取合约中的资产. 2) 调用堆栈溢出漏洞 调用堆栈溢出指调用链的堆栈深度超过虚拟机限制的最大深度, 导致堆栈溢出而引发异常, 进而产生异常处理漏洞. 该漏洞曾是GovernMental游戏存在的漏洞, 该游戏在合约设计阶段未考虑堆栈限制, 导致约1100以太币被冻结(https://www.reddit.com/r/ethereum/com ments/4ghzhv/governmentals_1100_eth_jackpot_payout_is_stu ck). 自2016年8月16日起, 调用堆栈溢出漏洞已被以太坊通过设置EVM调用堆栈的最大可达深度总小于1024来加以解决. 2.2.3 区块链层安全漏洞 区块链维护一个分布式数据账本, 智能合约通过虚拟机与区块链进行交互, 如提交事务信息、获得合约信息或区块链状态等‎[9]. 智能合约涉及的金钱变动、事务状态修改或合约销毁等操作最终均呈现在区块链中, 处于这个层次的漏洞通常与区块链自身特性有关, 例如矿工修改区块链信息, 包括时间戳、块难度、块号或事务执行顺序等, 可能会导致产生时间戳依赖等漏洞. (1) 区块信息依赖类漏洞 区块信息依赖指合约执行敏感操作的条件依赖于区块链信息, 而该信息能够被矿工影响或控制, 导致合约的执行与预期设计不符. 区块链能够提供与区块相关的信息, 如合约所在区块的块号、块难度、块哈希值以及时间戳等, 但以太坊允许矿工在一定范围内修改以上信息, 因此如果合约将区块信息作为判断合约执行逻辑的条件, 可能导致恶意攻击者(即恶意矿工)操纵合约执行, 使攻击者获利. 该类漏洞可分为两种, 即时间戳依赖漏洞和区块属性依赖漏洞. 1) 时间戳依赖漏洞 时间戳依赖漏洞指合约在执行关键操作(例如转移货币、决定游戏胜者等)时, 在判断条件、生成随机数、影响货币价格或交易时间等操作中将区块时间戳作为参数. 矿工在挖掘出新的区块后, 可以在一定范围内决定该区块的时间戳, 具体来讲, 矿工可在900 s内修改时间戳, 因此恶意矿工可以根据时间戳控制关键操作的触发条件, 从而对合约进行攻击. 2) 区块属性依赖漏洞 区块属性依赖漏洞与时间戳依赖漏洞相似. 区块属性指合约所在区块的块号、块难度、块哈希值等信息, 由于矿工能够在一定范围内影响区块属性, 因此如果合约将区块属性作为执行关键路径的条件, 或者用于生成随机数, 则可能会被恶意矿工控制. (2) 事务状态类漏洞 事务状态类漏洞指由于区块链对事务状态的处理与预期不符, 或事务信息被攻击者获得, 进而对合约运行造成影响. 该类漏洞可分为3种, 即不可预知状态、保守秘密和事务顺序依赖漏洞. 1) 不可预知状态漏洞 不可预知状态漏洞指区块链短分支上的事务可能会被回滚, 因此合约事务的执行处于未知状态. 区块链中, 当多个矿工同时发现一个新的有效区块时, 区块链会分为多个分支, 一段时间后, 最长的分支会被保留, 而短的分支上的事务会被回滚, 导致合约处于未知状态. 2) 保守秘密漏洞 保守秘密漏洞指合约的状态变量没有通过加密手段进行处理, 可能被攻击者解析或推断从而影响合约运行. 由于区块链具有公开性, 设置变量的操作会通过事务发布到区块链上, 因此攻击者可以通过事务推断该变量的值. 为了保守变量的秘密, 开发者应该使用合适的加密手段对状态变量进行保密处理, 如使用定时承诺‎[66]等. 3) 事务顺序依赖漏洞 事务顺序依赖漏洞指合约因事务没有按照预想顺序执行, 导致合约的调用出现错误而引起逻辑错误. 区块链会在每个周期更新自身状态, 一个周期中存在一系列事务, 而这些事务的先后顺序由矿工决定, 因此调用者无法获知事务执行时的合约状态, 如果执行的预期结果依赖于事务顺序, 则可能导致代码逻辑错误. 2.3 安全漏洞检测情况分析 各种检测方法能够检测到的智能合约安全漏洞种类不同, 前文表3中列出了可检测相关漏洞的工具或方法对应文献. 结合表3以及第2.2节中对漏洞的分类与描述, 本节对漏洞检测情况进行分析、比较和评价. 从表3中可以看出, 大部分检测工具均支持检测重入漏洞. 重入漏洞是导致TheDAO事件的关键漏洞, 造成了6000万美元的损失. 以太坊为了修复这次攻击造成的影响, 造成了以太坊硬分支, 甚至违背了区块链和以太坊宣言, 由此可见重入漏洞的巨大危害. 部分检测方法根据重入漏洞的特征设计了有效分析手段, 如ReGuard‎[36]使用重入状态机检测重入漏洞等. 变量操作类漏洞中的数值计算相关漏洞是一类常见漏洞. 由于合约代码中的数值变量通常与加密货币数量直接相关, 因此智能合约相比传统软件对数值数据更加敏感. 相比于其他检测方法仅能检测整型溢出漏洞, 基于符号执行的检测方法Osiris‎[57]能够检测更多种类的数值计算漏洞, 如截断漏洞和算术符号错误漏洞等. 贪心漏洞和权限控制类漏洞是造成Parity事件的主要漏洞. 攻击者通过权限控制类漏洞将Parity合约的子合约杀死, 导致Parity合约的金钱无法转移, 引发贪心漏洞, 造成3亿美元以太币被冻结. 这两种漏洞也能被大部分检测工具所检测. 异常处理类漏洞和区块信息依赖类漏洞也被大部分检测工具关注. 由于智能合约通常涉及货币转移, 因此send、transfer和delegatecall这3个函数的返回值处理尤为重要. 时间戳或区块属性的信息通常被当作执行敏感操作的条件, 而该类信息可能受外界影响甚至能够被恶意矿工控制, 从而影响合约的运行. 在高级语言层, 目前还缺少针对交互类和影响合约鲁棒性类漏洞的检测方法. 此外, 虚拟机层和区块链层与智能合约的执行环境相关, 短地址、不可预知状态及保守秘密等虚拟机和区块链层的漏洞可能会对智能合约造成较大安全隐患, 但还缺少有效的检测方法. 未来对智能合约安全漏洞检测方法的研究可考虑针对上述类型的漏洞进行分析. 3 智能合约安全漏洞分析与检测 漏洞分析与检测是检测方法的核心步骤. 在漏洞模式识别完成后, 需要通过合适的检测方法收集与漏洞相关的程序信息, 根据程序信息对程序是否存在漏洞进行判断. 部分实证研究‎[28]将检测方法分为4种类型, 即符号执行、动态模糊测试、深度学习和静态分析. 现有智能合约安全漏洞综述‎[9,14–16]将检测方法分为5种类型, 即在上述4种类型的基础上添加了形式化验证方法. 本文参考已有的检测方法分类标准, 鉴于ContractWard‎[37]等方法采用随机森林等机器学习方法, 基于深度学习的分类无法覆盖这类文献, 故本文将基于深度学习的分类标准调整为基于机器学习的智能合约安全漏洞检测方法. 为了更清晰地梳理各种类型检测方法的研究进展, 本节以研究问题为主线, 分别提出关于定义、技术分类以及技术分析3个研究问题, 具体的研究问题如下. RQ1: 该类型检测方法在传统软件和智能合约中的定义是什么? 有什么异同? RQ1关注各个检测方法的定义. 智能合约运行在区块链这一新兴的运行环境中, 具有与传统软件不同的结构和运行机制, 因此需要将符号执行、模糊测试以及静态分析等应用在传统软件的漏洞检测方法经过改进后再应用到智能合约中才能取得较好的检测效果, 该问题关注应用在智能合约中的检测方法与传统软件中的检测方法的异同. 本文将该问题的回答总结在各个检测方法描述的开头中. RQ2: 该类型检测方法相关的研究成果可分为哪几种类型? RQ2关注各个检测方法的技术分类. 本文根据关注的问题不同, 将5种类型的检测方法进一步整理为更细粒度的技术分类, 并按照时间顺序逐条梳理了各个技术分类的定义与研究进展. 本文将该问题的回答总结在各个检测方法的各个小节中. RQ3: 在RQ2的基础上, 这些方法类型之间的优势和局限性是什么? RQ3关注各个技术分类的特点、优势与局限性. 在RQ2的基础上, 本文进一步对已有的研究进展进行归纳, 提出各个技术分类的特点, 并与其他技术分类进行比较, 从泛用、性能与准确性等角度总结了优势与局限性. 本文将该问题的回答总结在各个检测方法的小结部分中. 3.1 基于符号执行的智能合约安全漏洞检测 符号执行是一种程序分析技术‎[67], 使用符号化输入代替具体输入, 模拟执行被分析程序, 在分析过程中将程序中的操作转化为相应符号表达式, 利用SAT/SMT求解器对路径条件可满足性求解, 可用于分析路径可达性、生成测试数据及检测特定漏洞等. 在智能合约领域, 基于符号执行的检测方法通常以字节码作为输入, 对字节码进行分析或将字节码转换为中间语言再进行分析. 本文根据分析对象的不同, 将目前已有的检测方法分为两种类型, 即基于源代码/字节码的符号执行和基于中间语言的符号执行. 3.1.1 基于源代码/字节码的符号执行 基于字节码的符号执行通过将字节码反编译为操作码, 根据操作码构建EVM堆栈并符号化执行程序. Luu等人‎[11]归纳总结了4种以太坊智能合约的安全漏洞, 构建了基于符号执行的检测工具Oyente, Oyente以字节码作为输入, 逐行分析字节码构建控制流图(control flow graph, CFG), 利用约束求解器去除不可达路径, 探索可达路径. 在分析阶段, 对4种漏洞构建漏洞模型, 并基于漏洞模型对智能合约进行检测, 从而识别存在漏洞的合约. Oyente的实验结果表明, 在收集到的19366个合约中, 8833个合约至少含有一种漏洞, 表明智能合约普遍存在安全问题. Oyente具有良好的扩展性, 其提供的符号执行框架为后续研究工作提供了基础. 如Nikolić等人‎[54]、Wang等人‎[27]以及Chen等人‎[62,63]分别基于Oyente构建了Maian、Artemis、Gasper以及GasReducer. 借助Oyente收集的路径约束条件, Maian总结了贪心性、挥霍性和自杀性3种以太币流动的特征, 建立漏洞模型并对其进行检测. Artemis则在符号执行中收集了操作码序列、函数调用和块信息调用等信息, 扩展了贪心、区块属性依赖、无gas发送和delegatecall函数调用未知调用函数这4种漏洞的检测逻辑. Gasper和GasReducer关注gas的消耗问题, 前者根据符号分析以及CFG判断是否存在任何条件下均不会被执行的死代码(dead code), 后者能够检测字节码中是否存在高gas消耗模式, 并通过EVM指令替换的方式将其转换为gas消耗更低的字节码. 随着智能合约代码规模的增加, 可能带来路径爆炸以及约束求解耗时较高等问题, 降低了安全漏洞检测效率. 针对这些问题, 研究人员尝试优化符号执行的路径探索策略, 例如利用静态分析等方法减少搜索空间, 或通过并行的方式提高符号执行的效率. 例如Chang等人‎[55]构建了sCompile, 将金钱相关的路径作为关键路径, 根据安全属性计算优先级分数, 再利用符号执行判断关键路径的可达性, 实验结果表明sCompile平均检测单个合约的时间仅需5 s, 且误报率较低. So等人‎[21]和Zhang等人‎[68]针对事务序列的组合爆炸问题, 分别提出了SmarTest和MPro, SmarTest从已知存在漏洞的事务序列中学习统计模型, 并基于该模型制导符号执行, 优先执行更可能触发漏洞的事务, MPro则根据RAW (read after write, 写入后读取)函数依赖信息剪枝事务序列的组合数量, 从而达到更高的检测效率. Mossberg等人‎[69]构建了动态符号执行工具Manticore, 以符号化表示事务序列的数据以及以太币数量, 有效地减少约束求解效率较低的问题, 实验结果表明Manticore能够达到更高的代码覆盖率. Zheng等人‎[70]提出了并行符号执行框架Park, 当符号执行遇到JUMP等指令时, 将创建新进程并复制当前EVM状态, 为在多个进程间共享全局变量, Park将序列化的全局变量存储至共享内存, 当需要读取时再将其重构, 以维护整个符号执行过程中的全局变量符号值, 实验结果表明Park-Oyente相较于Oyente在检测速度上提高了6.84倍. 此外, 研究人员通过挖掘常见安全漏洞的特征, 提出了多种具有针对性的漏洞分析和检测方法. 例如Torres等人‎[57]和So等人‎[26]针对整型溢出、除零或模零等数值相关漏洞, 分别提出了Osiris和VeriSmart方法, 前者利用污点分析检测数值相关漏洞, 后者求解不变量以验证安全属性. Grossman等人‎[71]和Rodler等人‎[38]针对重入漏洞提出了ECFChecker和Sereum, 前者识别事务能否通过非回调的方式实现等效的状态转换以检测漏洞, 后者则用于保护已部署的智能合约, 利用动态污点分析技术监控事务执行时的数据流, 根据重入漏洞模式识别并阻止可疑事务执行, 以避免引发重入漏洞. Liu等人‎[72]针对权限控制漏洞构建了SPCon, 通过挖掘历史事务交易构建权限控制模型, 利用符号执行和污点分析收集信息流信息并对合约进行检查, 以识别与用户权限相关的潜在漏洞. 赵伟等人‎[39]详细描述了对于重入、整型溢出等4种漏洞的定义和约束条件, 基于字节码构建CFG并利用约束求解器生成测试用例. Zhou等人‎[58]构建了SASC, 根据源代码生成调用关系图, 通过符号执行和语法分析检测和定位漏洞. Krupp等人‎[61]定义了两种漏洞模式, 一种是事务如果涉及关键路径的执行则可能存在漏洞, 另一种是如果合约涉及状态变量改变则可能存在漏洞, 并提出了TeEther对其进行安全漏洞检测, 实验结果表明38757个合约中, 815个合约存在上述两种漏洞. Chen等人‎[19]构建了DefectChecker, 根据CFG以及金钱相关调用、循环体和支付函数3种特征检测重入等8种安全漏洞, 同时利用该工具对智能合约进行了大规模的评估, 结果表明15.89%的智能合约含有至少1种漏洞, 其中占比最高的类型为未检测CALL返回值漏洞. 3.1.2 基于中间表示的符号执行 基于中间表示的符号执行方法在输入与检测步骤之间加入代码转换步骤, 将使用不同语法的代码转换为中间表示形式, 或将漏洞定义以中间表示形式描述. 通过该方法可将代码形式与检测方法分离, 达到更高的可扩展性. 基于中间表示能够实现跨平台的漏洞检测, 例如Jin等人‎[73]构建了ExGen, 将以太坊和EOS平台的代码均通过中间形式进行表示, 其中, 将以太坊平台的Solidity智能合约根据AST映射到LLVM类型中, 将EOS平台的C++智能合约直接转换为LLVM的中间表示, 通过对中间表示进行符号执行, 实现不同平台智能合约的漏洞检测. 此外, 漏洞定义也可通过中间表示进行描述. 例如Tsankov等人‎[40]构建了Securify, 使用特定领域语言作为中间表示, 用以定义漏洞模式, 再通过符号执行提取代码中精确的语义信息, 与漏洞模型进行匹配检测安全漏洞. Feng等人‎[41]构建了基于摘要的符号执行工具SOLAR, 将智能合约转换为中间表示, 添加了符号变量和符号表达式以便于符号执行, 同时利用自定义的查询语言定义漏洞模式, 该语言由uses、matches和where这3个部分组成, 分别用以定义变量类型、语句序列和匹配语句的约束条件. 3.1.3 小 结 表4将上述基于符号执行的智能合约安全漏洞检测方法的类型、名称、检测对象以及主要特征进行了总结, 并根据发表年份进行排序. 目前, 现有基于符号执行的检测方法通常以字节码作为输入, 通过字节码(或反编译后的操作码)构建EVM堆栈, 并根据堆栈符号化执行程序, 在符号化执行过程中收集程序信息, 包括路径条件、变量修改或合约调用等. 部分检测方法如DefectChecker会根据程序信息进一步建模为程序特征(如金钱调用、循环体或外部调用等), 然后根据预先定义的属性或漏洞模式检测是否存在漏洞. 表 4 基于符号执行的智能合约安全漏洞检测方法小结 基于源代码或字节码的检测方法直接将字节码作为输入, 或将源代码通过编译器转换为字节码后进行分析, 是基于符号执行的检测方法中的常用做法, 相对于基于中间表示的检测方法, 其易于理解且不存在因中间表示不准确而改变代码语义的风险. 基于中间表示的检测方法在基于源代码或字节码的检测方法步骤前加入了转换为中间语言的过程, 或引入特定领域语言作为漏洞模式定义的中间表示, 相对于直接对字节码进行分析, 该类方法提升了可扩展性, 但需要注意转换后的中间表示应与原始代码或漏洞定义的语义保持一致. 3.2 基于模糊测试的智能合约安全漏洞检测 模糊测试是一种通过向目标系统提供非预期输入并监视异常结果来发现软件漏洞的方法. 它使用半有效的数据作为程序输入, 以程序是否出现异常作为标志, 可发现程序中存在的安全漏洞‎[74]. 在智能合约领域, 相关研究工作主要分为两种类型, 即覆盖制导的模糊测试方法和目标制导的模糊测试方法. 3.2.1 覆盖制导的模糊测试 覆盖制导的模糊测试方法以达到更高的代码覆盖率为目的‎[75]. 由于智能合约可通过函数访问的方式修改状态变量, 而部分程序状态(或代码)需要满足特定的状态变量条件, 即通过特定的函数调用序列才能到达(如需先执行权限控制函数指定合约拥有者, 才能覆盖到仅允许合约拥有者才能执行的代码). 因此, 测试用例执行序列是覆盖制导模糊测试方法的研究重点之一. 在智能合约领域, 覆盖制导的模糊测试方法通过模仿学习、动态数据依赖分析等策略生成事务调用序列, 按照该序列对智能合约进行模糊测试, 覆盖更深层的程序状态, 提高代码覆盖率. 现有工作中, Jiang等人‎[12]和Huang等人‎[76]分别提出了以太坊和EOS平台智能合约的模糊测试框架, 即ContractFuzzer和EOSFuzzer. ContractFuzzer和EOSFuzzer均通过应用程序二进制接口(abstract binary interface, ABI)获得函数参数并生成测试用例, 分别通过插装EVM或WebAssembly虚拟机收集智能合约的执行情况, 再结合漏洞模式检测重入等安全漏洞. 在此基础上, Liu等人‎[36]针对重入漏洞构建了ReGuard, 在模糊测试过程中记录函数调用、返回值、对存储的访问、区块链的参数和分支操作等漏洞相关信息, 通过自动机识别重入漏洞. Wüstholz等人‎[22]构建了灰盒模糊测试工具Harvey, 使用需求驱动的序列模糊技术, 识别需要多个事务执行才能覆盖到的分支, 生成能够覆盖到更深层次程序状态的序列, 从而达到更高的覆盖率. Choi等人‎[77]构建了Smartian, 考虑了事务序列对代码覆盖的影响, 根据定义使用关系生成事务序列, 并将能够覆盖更多数据流的测试用例保留作为种子, 实验结果表明Smartian能够在较短时间内覆盖更多合约代码. 优化事务序列的生成或变异策略是提高模糊测试效率的重点之一. Xue等人‎[78]在sFuzz‎[20]的基础上, 针对跨合约场景构建了模糊测试工具xFuzz, 将存在依赖关系的智能合约部署在测试环境中, 利用机器学习和适应度评价指标筛选需要执行的函数, 减少事务序列组合数量并提高模糊测试效率. Liu等人‎[79]构建了IR-Fuzz, 根据状态变量的读写情况计算次序优先级分数, 根据排序后的分数生成事务序列, 同时IR-Fuzz定义了稀有分支和疑似漏洞分支, 并分配更多的测试资源以提高漏洞检测效率. 此外, 符号执行能够提供高质量的测试用例, 且能够对路径约束条件进行求解. 现有研究工作尝试学习符号执行生成的测试用例, 或利用符号执行解决难以覆盖复杂路径分支的问题, 从而提高模糊测试的效率. 例如He等人‎[56]实现了基于模仿学习的模糊测试工具ILF (imitation learning based fuzzer), 利用模仿学习框架学习符号执行生成的能够提高覆盖率的测试用例序列, 从而生成新的测试用例序列, 利用这种方法能够覆盖到更深层次的代码状态. Torres等人‎[42]和Chen等人‎[80]将模糊测试与符号执行结合, 分别提出了ConFuzzius和WASAI, 利用符号执行求解尚未覆盖的分支路径条件, 将求解的结果用于变异测试用例, 有效地提高了智能合约的代码覆盖率. 3.2.2 目标制导的模糊测试 目标制导的模糊测试方法以覆盖部分分支或程序块为目标, 弥补了模糊测试难以覆盖稀有路径的不足‎[81]. 在智能合约领域, 基于目标制导的模糊测试方法通常在覆盖制导的方法基础上, 通过语义分析或适应度函数等方式确定目标, 基于目标对种子的分配做出调整, 从而达到更高的测试效率. 例如Wüstholz等人‎[82]提出了目标制导的灰盒模糊测试工具, 在模糊测试过程中根据语义分析获得目标信息, 判断当前路径分支能否覆盖到目标节点, 根据种子能够覆盖到的路径对测试用例的生成策略做出调整. Nguyen等人‎[20]设计了轻量级的自适应度策略, 构建了模糊测试工具sFuzz. sFuzz在模糊测试中仅监视与漏洞相关的信息, 相对于ContractFuzzer更加轻量. 在测试用例选择阶段, sFuzz设计了目标函数, 用于度量测试用例与条件严格路径之间的距离, 保留与严格路径距离最短的测试用例, 采用AFL ()中的交叉和变异方法生成新的测试用例, 与Oyente和ContractFuzzer相比, 能够有效提高测试效率. 3.2.3 小 结 表5将上述基于模糊测试的智能合约安全漏洞检测方法的类型、名称、检测对象以及主要特征进行总结, 并根据发表年份进行排序. 目前, 现有的模糊测试方法通常需要构建模糊测试运行环境(如以太坊运行环境、EOS测试网络等), 并在模糊测试过程中收集智能合约执行情况(如通过event反馈、虚拟机插桩等), 部分方法还将根据收集或分析的信息对测试用例生成进行调整(如sFuzz‎[20]、文献‎[82]的目标制导方法等), 或结合其他方法(如符号执行)达到更高的代码覆盖率或更深层次的程序状态. 表 5 基于模糊测试的智能合约安全漏洞检测方法小结 覆盖制导的模糊测试方法将研究的重点放在如何生成能够覆盖更深层次程序状态的测试用例序列上, 该类方法首先利用其他分析方法获得初始种子测试序列, 然后通过模仿学习等方式达到覆盖更深层程序状态的目的. 目标制导的模糊测试方法将研究的重点放在如何确定目标, 并生成能够覆盖目标的测试用例上, 该类方法通过收集分析语义信息和路径执行情况对测试用例生成策略进行调整, 能够通过迭代不断地提高代码覆盖率, 从而识别更多的漏洞. 3.3 基于机器学习的智能合约安全漏洞检测 近年来, 机器学习尤其是深度学习技术的发展, 吸引了较多的研究者利用机器学习技术解决软件中的安全问题‎[83], 例如Alhuzali等人‎[84]利用深度学习识别和生成动态Web应用中的漏洞, Melicher等人‎[85]利用神经网络对密码安全性进行检测等. 在智能合约领域, 也有研究者利用机器学习方法进行了一系列研究, 如吴雨芯等人‎[86]利用注意力机制和双向长短记忆神经网络对智能合约进行分类等. 在智能合约安全领域, 研究者采用多种数据表征方式将智能合约转换为合适的数据形式, 并构建相应模型以检测智能合约中的漏洞. 本文参考顾绵雪等人‎[83]对基于深度学习漏洞挖掘技术的分类标准, 根据在数据表征阶段数据解析的表示结构不同, 将基于机器学习的检测方法划分为3类, 即基于图表征的检测方法、基于序列表征的检测方法和基于文本表征的检测方法. 3.3.1 基于图表征的检测方法 基于图表征的检测方法将源代码或字节码转换为控制流图、数据流图等表示形式, 有效地抽象出智能合约代码的控制流逻辑、数据依赖关系等信息. 基于图的表征形式能够反映代码中的语法和语义特征, 从而使机器学习模型达到更优的检测性能. 控制流图和数据流图包含丰富的上下文信息, 可用于获取智能合约的语法语义, 以检测安全漏洞. 例如Zhuang等人‎[43]提出了DR-GCN和TMP, 结合数据流图和控制流图构建合约图, 在图规范化处理后, 构建图卷积神经网络DR-GCN和时序消息传播网络TMP (temporal message propagation network)用于检测安全漏洞, 实验结果表明DR-GCN和TMP优于Oyente‎[11]等检测方法. 在DR-GCN和TMP的基础上, Liu等人‎[87]进一步结合了图神经网络与专家知识, 定义了时间戳声明等专家模式特征, 与图神经网络获得的合约图特征结合, 以提高模型对漏洞检测的准确性. Zhang等人‎[88]提出了ReVulDL, 在利用图神经网络检测出重入漏洞后, 使用解释分析方法pos-hoc获取模型用于判断重入漏洞的重要边, 从而定位重入漏洞所在的位置. 3.3.2 基于序列表征的检测方法 基于序列表征的检测方法对源代码或字节码进行词法分析, 提取变量访问、函数调用序列或语句调用序列等特征信息, 利用记忆力机制等方法学习提取出的序列信息, 构建长段记忆模型等对未知合约代码进行安全漏洞检测. 现有方法通常先利用嵌入算法将智能合约的代码、字节码或AST转换为向量, 再构建序列模型检测安全漏洞. 例如Tann等人‎[89]和Qian等人‎[44]利用代码嵌入算法分别将字节码和源代码转换为向量, 构建递归神经网络模型, 利用长段记忆以及注意力机制学习句法结构、语法语义等上下文关系, 能够检测重入等安全漏洞. Liu等人‎[60]提出了s-gram, 利用静态分析获得状态变量访问依赖和敏感流等语义信息, 将源代码和语义信息结合, 从中获得基于AST节点类型的Token信息, 并将其转换为Token序列, 基于Token序列构建n-gram语言模型, 以检测未知代码中是否含有漏洞. 3.3.3 基于文本表征的检测方法 基于文本表征的检测方法直接将源代码或字节码作为文本处理, 利用词嵌入算法(如Word2Vec、FastText‎[31]等)将文本转换为向量, 基于向量构建机器学习模型学习隐藏在代码中的语法语义信息, 用以检测智能合约安全漏洞. 为了使机器学习模型能更好地学习代码结构或漏洞信息, 现有方法通常会在分析阶段进行规范化处理, 或利用切片等技术减少噪音干扰. 例如Wang等人‎[37]构建了ContractWard, 通过n-gram从字节码中得到特征, 基于随机森林、支持向量机等5种机器学习算法构建模型, 使用SMOTE‎[90]和SMOTE-Tomek‎[91]采样方法处理类不平衡问题, 对重入、整型溢出等6种漏洞进行检测, 实验结果表明ContractWard的召回率达到了96%. 相似地, Yu等人‎[92]构建了DeeSCVHunter, 通过FastText嵌入算法将代码转换为向量, 构建Bi-GRU等深度学习模型进行漏洞检测. 不同之处在于, DeeSCVHunter定义了与漏洞相关的特征函数或变量(如时间戳依赖漏洞与block.timestamp相关), 将代码与漏洞特征匹配, 获得候选语句, 再进一步利用控制流和数据流将与CS相关的语句添加到漏洞候选切片(vulnerability candidate slice, VCS)中. 在模型训练阶段, DeeSCVHunter仅将VCS转换为向量表示, 减少了代码噪音的干扰. 3.3.4 小 结 表6将上述基于机器学习的智能合约安全漏洞检测方法的类型、名称、检测对象以及主要特征进行了总结, 并根据发表年份进行排序. 基于机器学习的智能合约安全漏洞检测方法通过将智能合约代码转化为能够被模型识别的特征, 构建机器学习或深度学习模型学习漏洞模式, 通过模型对未知智能合约进行安全漏洞检测. 表 6 基于机器学习的智能合约安全漏洞检测方法小结 基于图的特征表示方法将智能合约代码转换为控制流图、调用图等, 通过图卷积神经网络或时序信息传播网络等学习图中的信息. 该类方法能够有效地抽象出深层次的代码特征, 但图的构造过程较为复杂, 对于存在复杂逻辑或调用关系的合约, 存在分析阶段耗时较长的问题. 基于序列的特征表示方法对源代码中进行分析, 获得语法、语义或控制流等信息, 关注执行路径、调用序列等特征, 将其作为深度学习模型的输入构建序列模型. 该类方法学习到的序列表征与漏洞模式的关联性较强, 但由于其需要大量训练数据, 故检测效率较慢. 基于文本的特征表示方法利用自然语言处理中的词嵌入算法, 将智能合约转换为词向量, 或通过n-gram提取智能合约代码中的特征, 并基于向量特征构建模型进行检测. 该类方法直接将代码文本作为输入, 通常在分析阶段通过切片等方法过滤与漏洞不相关的程序片段, 从而提高模型的学习能力和检测能力. 因此, 如何在分析阶段使模型获得更多的语义信息是该类方法的关键点. 3.4 基于形式化验证的智能合约安全漏洞检测 形式化验证基于形式化规约, 利用数学方法证明程序的安全性. 此类方法通过对问题建立数学模型, 利用数学语言描述整个系统的全部状态, 从而验证程序在各个状态下是否保持安全性和可靠性‎[93]. 王赫彬等人‎[94]对以太坊智能合约安全形式化验证方法进行了调研, 从对智能合约代码分析的完备性和可应用性角度对已有的形式化验证方法进行了分析比较. 朱健等人‎[95]从形式化方法、语言、工具和框架的角度出发, 分析了自动化验证、转换一致性等关键问题. 与以上2篇文献侧重的方向不同, 本文从安全漏洞检测的角度出发, 分析形式化验证方法在智能合约安全漏洞检测方面的优势和局限性. 本文参考文献‎[94]中的分类标准, 将基于形式化验证的安全漏洞检测方法分为3种类型, 即定理证明、模型验证和其他形式化验证方法. 3.4.1 基于定理证明的检测方法 定理证明指将智能合约代码定义为可被定理证明器识别并编译的语言, 然后通过定理证明器(如Isabelle/HOL‎[96]、Coq (https://coq.inria.fr/)等)和预定义的安全属性对智能合约进行安全检测. 例如Hirai‎[97]利用Lem语言定义EVM, 基于Isabelle/HOL实现了形式化验证方法. 利用该定义, Hirai发现了在gas计算问题上以太坊黄皮书‎[64]和EVM实现之间的差异, 并向以太坊官方提出了修改意见. Yang等人‎[98]开发了基于Coq定理证明器的形式化验证工具FEther. FEther基于Solidity的子集Lolisa开发, 保证了智能合约和形式化语言之间的一致性. FEther包含了一套自动化策略, 将符号执行和高阶逻辑定理证明结合, 能够自动执行和验证智能合约. 3.4.2 基于模型验证的检测方法 模型验证方法将智能合约程序解析为状态或行为并构建程序模型. 在验证阶段从初始状态或行为出发, 若状态转移不符合规约则可能存在安全漏洞. 基于模型验证的智能合约检测方法通常采用有界模型验证, 即验证一定状态转移步数内模型是否违背规约. Boogie是一种编程语言的中间表示, 可将源代码转换为更简单、更容易验证的形式. 基于模型验证的现有工作中, 常将Solidity源代码转换为Boogie后再进行模型验证等步骤. 例如Wang等人‎[99]构建了VeriSol, 将Solidity智能合约转换为Boogie形式, 同时将工作流表示为有限状态机, 使用Corral对Boogie形式的智能合约进行有界模型验证, 用于验证工作流与智能合约代码之间的语义是否一致. 与VeriSol相似, Antonino等人‎[100]构建了Solidifier, 将Solidity的简化语言Solid描述的智能合约转换为Boogie形式, 同样使用Corral进行有界模型验证, 能够检测用户定义的程序错误. 3.4.3 其他形式化验证方法 除定理证明和模型验证外, 还有其他类型的形式化验证方法. 有研究者构建了针对智能合约的形式化验证框架, 并基于这些框架对智能合约进行分析和安全检测. 例如Bhargavan等人‎[101]构建了智能合约验证框架F*, 该框架通过Solidity*和EVM*这两个模块将源代码和字节码转换为F*框架可识别的语言, 以检测合约的安全性和功能正确性. Grishchenko等人‎[45]在F*的基础上针对智能合约的安全问题进行了分析, 验证了调用完整性、参数不受矿工控制等安全属性. Hildenbrandt等人‎[102]在K框架的基础上构建了KEVM框架, 该框架可用于分析和验证智能合约程序. KEVM考虑了EVM的异常机制, 构建了支持异常的控制流图, 可用于对gas消耗和白皮书中的EVM语言规范进行分析. 此外, 还可针对特定属性进行形式化分析, 通过该属性对智能合约进行安全检测. 例如Permenev等人‎[103]提出了VerX方法, 将智能合约中的时序属性转换为可达性验证, 并构建了针对EVM的符号执行引擎, 结合延迟谓词抽象方法, 在事务执行过程中使用符号执行, 在事务之间使用抽象分析, 以对智能合约的功能属性进行验证. Kalra等人‎[46]构建了检测工具ZEUS, 将智能合约代码翻译为LLVM中间语言形式, 利用LLVM框架进行抽象解释和符号模型检查. 由于以LLVM中间表示作为输入, 因此ZEUS能够支持多种语言编写的智能合约. 3.4.4 小 结 表7将上述基于形式化验证的智能合约安全漏洞检测方法的类型、名称、检测对象以及主要特征进行总结, 并根据发表年份进行排序. 形式化验证方法通常将智能合约转换为定理证明器、模型验证工具或自定义框架能够识别的输入, 并根据断言或预先定义的安全属性对程序进行验证. 表 7 基于形式化验证的智能合约安全漏洞检测方法小结 定理证明和模型验证属于形式化验证领域的常用方法, 由于其不依赖于智能合约语言, 仅需提供验证工具能够识别的输入即可完成验证, 具有适用范围广的优点. 然而, 形式化验证存在自动化程度低、人工成本高的不足, 如何构建能够自动分析和验证智能合约安全性的工具是形式化验证未来可探讨的方向之一. 部分研究者根据智能合约或运行环境的特性构建了具有针对性的验证框架, 以更准确地对智能合约程序进行建模, 但这类方法的可扩展性通常较弱, 如F*、KEVM和VerX等框架仅支持对Solidity合约的验证, 未来可探索并尝试构建支持其他智能合约语言的形式化验证框架. 3.5 基于静态分析的智能合约安全漏洞检测 静态分析指在不运行软件的前提下对软件进行分析‎[104], 基于静态分析的漏洞检测方法通过分析程序的代码、结构或文档等信息挖掘漏洞‎[105]. 在智能合约领域, 基于静态分析的检测方法通常以源代码作为输入, 通过将源代码转换为中间形式(例如XML等), 然后基于预先定义的漏洞模式行匹配, 或者利用数据流、控制流或逻辑关系分析等方法对智能合约的安全漏洞进行检测. 根据静态分析检测漏洞的判断依据不同, 本文将基于静态分析的智能合约安全漏洞检测方法分为两种类别, 即基于匹配的检测方法和基于语义的检测方法. 3.5.1 基于匹配的检测方法 基于匹配的方法通常首先将智能合约转换为中间表示, 如图、XML或词向量等形式, 然后将其与预先定义的漏洞模型进行相似性匹配. 模式匹配或关键字匹配是一种简单直接的检测方法. Tikhomirov等人‎[47]构建了SmartCheck, 将Solidity源代码转换为基于XML的中间表示, 使用XPath匹配预先定义的漏洞模式, 从而检测是否存在漏洞. Zhang等人‎[48]构建了基于正则表达式匹配的检测工具SolidityCheck, 对源代码的格式进行预处理, 删除空行、空格以及注释等, 然后针对不同的漏洞类型预定义关键字, 对关键字进行正则表达式匹配. 上述方法在漏洞检测过程中, 由于严格按照漏洞模式或关键字进行匹配, 可能导致较多的误报和漏报. 在此基础上, 利用代码嵌入技术将源代码或字节码转换为向量, 再通过距离度量的方法匹配漏洞片段, 能够提高漏洞检测的性能. Gao等人‎[25,106,107]构建了SmartEmbed, 将源代码根据粒度划分为合约级、函数级和语句级3种带有代码结构信息的段落, 利用代码嵌入技术将段落转换为向量, 比较向量之间的距离得出代码之间的相似性, 当合约与漏洞代码库中的代码片段相似度较高时, 则该合约可能含有特定类型的漏洞. 韩松明等人‎[49]和Huang等人‎[50]采用程序切片技术减少字节码中噪音对代码嵌入的干扰, 利用图嵌入算法将切片转换为向量, 并利用余弦距离计算向量之间的相似度, 用于字节码匹配和漏洞检查. 3.5.2 基于语义分析的检测方法 基于语义分析的方法收集并分析程序中的语义信息, 如逻辑关系、状态变量操作和敏感流等, 并基于以上分析结果对合约的安全性进行检测. 污点分析是一种常用的数据流分析方法, 其将外部输入的数据作为污点源, 追踪污点源的数据传播轨迹, 以判断程序的安全性. Feist等人‎[51]构建了Slither, 利用污点分析和数据流图获取智能合约代码中的信息, 如变量的读写情况等, Slither框架可用于漏洞检测、优化消耗资源较多的代码模式以及辅助用户理解代码逻辑. Gao等人‎[108]和Xue等人‎[52,109]分别构建了EasyFlow和Clairvoyance, 利用污点分析技术识别疑似漏洞路径, 并通过预定义的路径保护模式进一步过滤, 从而提高漏洞检测的准确性. Ghaleb等人‎[110]构建了eTainter, 将CALLDATALOAD等引入外部数据的指令作为污点源, 利用污点分析追踪外部数据的传播, 相较于MadMax, eTainter无需专家规则即可检测拒绝服务等安全漏洞. 智能合约的逻辑关系的可用于检测安全漏洞. Brent等人‎[53]和Grech等人‎[59]分别构建了Vandal和MadMax. Vandal利用特定领域语言Souffle‎[111]作为逻辑规范, 当需要分析新漏洞时, 可组合已有的规范从而达到较好的扩展性. MadMax则通过反编译器将EVM字节码转换为结构化中间语言, 结合基于逻辑分析的规范生成高级程序模型, 收集和分析数据流、循环和数据结构等信息用于检测相关漏洞. 此外, 控制流图同样包含丰富的智能合约语义信息. Albert等人‎[112]构建了EthIR, 优化了控制流图的生成, 相较于Oyente仅保存跳转地址的最后一个值, EthIR保存所有可能的跳转地址, 从而生成整个合约代码的CFG. EthIR分析堆栈变量、本地变量、合约字段和区块链数据, 进一步将CFG转换为基于规则的表示形式(rule-based representation, RBR), RBR可作为SACO‎[113]分析器的输入, 用以推断和分析gas消耗等属性. Liao等人‎[114]提出了静态分析方法SmartDagger, 反编译智能合约的字节码, 通过深度学习模型还原丢失的状态变量属性等语义信息, 能够检测重入、时间戳依赖等安全漏洞. 3.5.3 小 结 表8将上述基于静态分析的智能合约安全漏洞检测方法的类型、名称、检测对象以及主要特征进行了总结, 并根据发表年份进行排序. 静态分析方法通常以源代码作为输入, 通过对智能合约代码中的语义、语法、代码结构或Token序列等信息进行分析, 结合预先定义的漏洞模式对代码进行检测. 表 8 基于静态分析的智能合约安全漏洞检测方法小结 基于匹配的方法将智能合约程序和漏洞模式转换为中间表示, 如XML、词向量等, 将需要被检测的合约与漏洞模式进行相似性比较. 该类方法可扩展性强, 当发现新的漏洞模式时, 仅需提供漏洞片段即可支持检测新漏洞. 然而, 该类方法缺少对程序语法语义的描述, 具有误报率较高的不足. 基于语义分析的方法收集并分析程序中的数据流、控制流和敏感流等信息, 结合污点分析等方法对合约进行漏洞检测, 相比于基于匹配的检测方法, 具有更高的精确度, 但该类方法需要预先分析并定义漏洞模式, 对检测新漏洞的可扩展性较差. 4 数据集与评价指标 为对智能合约安全漏洞检测方法的有效性进行评估, 一方面需要真实的数据集作为数据支持, 另一方面需要设计合理、有效, 且能准确评估方法特点的评价指标. 从以上两方面出发, 本节总结了在智能合约安全漏洞检测领域中常被使用的数据集和评价指标. 4.1 数据集 数据集可以为后续研究者在构建工具, 或者对方法有效性进行评估时提供数据支撑. 根据有无漏洞标签可将现有智能合约数据集分为两类, 即有漏洞标签的数据集和无漏洞标签的数据集. 前者标记了智能合约是否存在漏洞, 部分数据集还会进一步标记漏洞的具体类型和位置, 此类数据集可用于对检测结果的正确性进行评估; 后者没有标记漏洞是否存在, 可用于对智能合约安全漏洞检测方法的可扩展性或性能等方面进行评价, 如评估检测方法能否应用到包含各类业务需求及代码逻辑的智能合约中, 或评估检测方法的效率等. 有漏洞标签的数据集中, 根据标注的方法不同可分为2类. 一种是研究者人工分析智能合约是否存在漏洞, 并根据分析结果对智能合约进行标记. 如Chen等人‎[24]组织专家对587个合约进行了人工漏洞检测; Durieux等人‎[34]根据DASP网站(https://dasp.co)的分类标准手动标记了69个合约中的漏洞, 该数据集中还包含漏洞的行数等信息. 另一种是研究者利用工具对智能合约进行大规模检测或注入漏洞构建数据集. 如杨慧文等人‎[115]利用智能合约在线检测平台对4203份源代码进行漏洞检测, 构建了包含37种代码度量元和21种漏洞信息的数据集; Ghaleb等人‎[116]构建了SolidiFI工具, 将7种漏洞注入到无漏洞的合约中, 以此生成存在漏洞的智能合约. 虽然基于工具标注的数据集标签存在标记错误的可能, 但仍可用于评估其他检测方法有效性. 本文整理部分文献中公开的有漏洞标签的数据集, 统计其标记方式、文献、名称、数据集地址以及数据集中标记的漏洞种类数量, 结果如表9所示. 其中, VeriSmart-benchmarks和prdc-contracts-evaluation包含多种来源的标签, 如CVE、ZEUS、SmartBugs和Slither等, 故未对漏洞种类进行统计. 表 9 存在漏洞标签的数据集 无漏洞标签的数据集可从EtherScan、BscScan (https://bscscan.com/)或XBlock ()等区块链浏览器或开源社区获得. 此外, 部分智能合约安全漏洞检测的实证研究工作也将自己收集整理的数据集开源, 供未来研究者使用, 如Durieux等人‎[34]收集Google BigQuery和EtherScan的智能合约代码, 将其开源在GitHub平台(https://github.com/smartbugs/smartbugs-wild); Ren等人[28]同样利用Google BigQuery和EtherScan收集智能合约代码, 并将去重后的44622份源代码开源在GitHub平台(https://github.com/renardbebe/Sm art-Contract-Benchmark-Suites). 4.2 评价指标 评价指标是衡量检测方法是否有效的重要依据. 在智能合约安全漏洞检测领域, 根据评估的方法阶段不同可分为两种类型, 即关注检测过程的评价指标和关注检测结果的评价指标. 关注检测过程的评价指标度量检测方法在分析和检测阶段的有效性, 例如检测耗时‎[22,68]、覆盖率‎[20,56]等. 检测耗时指在检测过程中, 从被测对象输入到检测结果输出的时间, 检测耗时越短, 表明检测方法的效率越高. 覆盖率指在检测和分析过程中, 源代码或字节码被分析或被执行的覆盖情况. 覆盖率越高, 表明检测方法越全面, 越有可能检测出漏洞. 关注检测结果的评价指标度量检测结果是否与真实情况相符, 例如准确率、召回率、F1值‎[19,92]以及AUC‎[37]等. 准确率(Accuracy)能够直观地评价工具的检测性能, 准确率越高, 表明漏洞的检测结果与真实情况越接近, 准确率的计算公式如公式(1)所示: $ Accuracy=\frac{TP+TN}{TP+FP+TN+FN} $ (1) 其中, TP、FP、TN和FN如表10的混淆矩阵所示. 表 10 智能合约安全漏洞检测结果混淆矩阵 TP: 检测结果为存在安全漏洞, 且真实情况也存在安全漏洞的智能合约数量. FP: 检测结果为存在安全漏洞, 但真实情况不存在安全漏洞的智能合约数量. FN: 检测结果为不存在安全漏洞, 但真实情况存在安全漏洞的智能合约数量. TN: 检测结果为不存在安全漏洞, 且真实情况也不存在安全漏洞的智能合约数量. F1值是综合考虑精准率(Precision)和召回率(Recall)的评价指标, 常用于二分类问题(即智能合约是否存在安全漏洞)时评价检测方法的有效性. 精确率和召回率的计算公式如公式(2)和公式(3)所示: $ Precision=\frac{TP}{TP+FP} $ (2) $ Recall=\frac{TP}{TP+FN} $ (3) F1值的计算公式如公式(4)所示, F1值越高, 表明方法在检测智能合约安全漏洞查准和查全的综合能力方面越优秀. $ F1=\frac{2\times Precision\times Recall}{Precision+Recall} $ (4) AUC (area under curve, 曲线下面积)指ROC (receiver operating characteristic, 接收者工作特征)曲线下的面积, ROC的横坐标为假阳性率(false positive rate, FPR), 纵坐标为真阳性率(true positive rate, TPR), FPR和TPR的计算公式如公式(5)和公式(6)所示. AUC的值越高, 表明安全漏洞的检测结果越与真实情况相符. $ {\mathit{FPR}}=\frac{FP}{TN+FP} $ (5) $ {\mathit{TPR}}=\frac{TP}{TP+FN} $ (6) 5 总结与展望 随着数字加密货币的快速普及与发展, 区块链技术逐渐成为工业界和学术界的研究热点. 智能合约作为区块链合约层的重要组成部分, 能够持有或转移资产, 实现如投票、众筹或游戏等诸多应用, 其潜在的巨大利益吸引了大量攻击者试图通过漏洞攻击智能合约, 窃取合约中的资产. 如何有效保障智能合约安全是目前软件工程和信息安全领域亟待解决的问题. 本文收集和分析了与智能合约安全漏洞检测相关的文献, 按照高级语言、虚拟机和区块链3个层次对已知的41种智能合约安全漏洞进行分类, 并将具有相似特征的漏洞进一步归纳为13种漏洞类型. 本文还将已有的智能合约安全漏洞检测方法分为符号执行、模糊测试、机器学习、形式化验证和静态分析5种类型, 分别介绍了各类型检测方法的原理、基本流程、特点、优势及局限性. 结合本文对智能合约安全检测领域研究进展的分析不难看出, 目前智能合约安全漏洞检测工作主要基于传统软件的漏洞检测方法, 如: 符号执行、模糊测试和静态分析等, 将其进行修改适配后应用于智能合约. 然而, 现阶段各类检测方法仍不够完善, 本文对未来的研究方向提出如下8点展望. (1) 目前的检测工作主要关注影响合约安全的漏洞, 而较少对性能相关漏洞进行检测, 例如可重用性、可扩展性或降低gas消耗等. 这类漏洞因不会直接影响智能合约运行或数字财产安全而不被研究者关注, 但性能相关漏洞会缩短智能合约的使用寿命、降低合约的鲁棒性或造成不必要的资源消耗, 同时可能增加出现安全漏洞的风险, 因此未来的研究工作可针对性能相关的漏洞进行检测, 或基于已有经验提出能够增加智能合约健壮性的编码建议等. (2) 目前的检测工作主要针对以太坊Solidity智能合约, 缺少对其他平台或其他语言编写智能合约的检测工作, 例如Linux基金会发起的Hyperledger开源区块链项目, 其支持Go、Java或Python等语言编写的智能合约, 而现有对这些平台的智能合约进行安全漏洞检测的工作较少, 且缺少针对其进行漏洞信息收集和分类的研究工作. Chen等人‎[24]的调查也表明, 在StackExchange平台中存在上千条关于其他平台智能合约的安全问题讨论. 未来的检测工作可先从漏洞发现与识别出发, 参考现有的流程框架收集相关漏洞信息, 然后构建有效的方法对其他平台的智能合约进行安全漏洞检测. 研究者可从2个角度出发构建检测方法, 一种是分析不同平台、不同类型智能合约的特性, 构建有针对性的检测方法; 另一种可以考虑将智能合约代码转换为中间语言或中间表示, 例如LLVM、AST或XML等, 并针对转换后的中间语言进行安全漏洞检测. 借助中间语言可以实现跨多种智能合约语言的安全检测, 但需要注意将智能合约转换为中间语言过程中保证语义的一致性. (3) 大部分检测工具将智能合约字节码作为输入. 字节码作为能够在以太坊中直接查看的数据, 通过字节码判断合约的安全性十分必要. 调用者可在使用前通过安全检测工具预先对合约的字节码进行安全检测. 需要注意的是, EVM执行字节码时可能会修改部分字节码语义, 例如没有返回值的函数会添加默认返回值0, 导致返回值的含义与调用者预期不同, 从而导致漏洞, 未来以字节码作为检测对象的研究工作需注意到EVM运行字节码引发的问题. (4) 符号执行通过分析操作码构建堆栈, 利用符号化输入模拟执行智能合约程序, 在遍历过程中搜集路径约束, 通过约束求解器去除不可达路径, 具有覆盖率高的优点. 然而, 与Java虚拟机指令不同, 以太坊虚拟机指令的跳转目的地址不会直接写出, 需要通过堆栈获取, 这是符号执行过程中分析函数间调用的难点之一. 此外, 由于状态空间和执行路径指数化增长, 符号执行面临着由于约束条件过多或过于复杂而导致的求解失败或检测效率过低等问题. Oyente可通过设置Z3求解时间或调用深度等方式限制检测时间在可接受范围, 但是将导致漏报率上升. 现有的检测方法中, 除Manticore外均基于静态符号执行对合约代码进行分析检测, 未来可考虑在符号执行中采用静动态结合的方式, 如通过动态符号执行简化需要求解的约束条件, 以缓解路径爆炸问题. 此外, 未来可尝试结合静态分析技术, 借助静态分析获得可能存在安全问题的代码位置, 将其设置为符号执行过程的重点目标, 从而提高检测效率. (5) 模糊测试通过构建区块链环境, 部署智能合约程序并生成测试用例执行合约, 具有误报率较低的优点, 并可使用测试用例重现检测到的漏洞, 便于开发和测试人员调试. 然而, 现有的模糊测试工具面临以下问题: 一是对漏洞的建模不够精确导致误报, 未来的模糊测试方法可考虑建立更精准的漏洞模型, 或构建验证器对可能触发漏洞的测试用例进行确认; 二是实证研究结果表明提高测试覆盖率不一定能够提高漏洞检测能力‎[28]. 未来的模糊测试工作可考虑如何高效组合测试用例形成调用序列, 以覆盖更深层次的程序状态, 从而更有效地发现漏洞. 此外, 目标制导的模糊测试方法在特定漏洞检测方面更具优势‎[117], 未来的研究工作可针对智能合约特定的漏洞构建目标制导的模糊测试方法. (6) 相较于其他方法, 基于机器学习的智能合约漏洞检测方法的优点在于无需构建漏洞模型. 但机器学习, 尤其是深度学习的不足在于缺少可解释性, 其检测结果难以为开发或测试人员提供具体信息. 未来工作可尝试构建可解释性更强的机器学习模型, 或利用解释分析方法分析模型的检测结果, 例如给出可能存在漏洞的代码位置等. (7) 形式化验证将程序作为输入, 通过构建形式化验证框架对智能合约的状态进行分析, 优点在于能够遍历合约所有可能的程序状态, 漏报率较低. 但部分形式化验证方法基于已有的形式化验证框架, 如定理证明器或模型验证器进行验证, 而将智能合约程序转换为形式化验证框架可识别输入的过程中可能导致语义缺失或修改. 未来的形式化验证方法可尝试构建能够支持直接描述智能合约语义的框架, 或增加一致性验证以保证原始合约与经过转换后的合约之间语义、功能及性能的一致性. (8) 静态分析通常以源代码作为输入, 通过计算待检测代码与漏洞代码之间的相似度, 或通过语义分析的方法获得程序信息, 判断是否存在漏洞. 静态分析的优点是检测效率较高, 但依赖于漏洞的表示形式以及模型的准确性, 且没有对路径可达性进行分析, 因此具有较高的漏报和误报率. 未来工作可将动态检测方法作为静态分析的补充, 对静态分析结果进行动态确认, 从而降低漏报和误报率. 本文梳理了智能合约安全漏洞检测的研究框架, 整理了在文献中出现的漏洞模式, 并归纳和分析了已有的检测方法, 对智能合约安全检测领域的研究方向提出了展望, 希望能够为未来的研究者提供参考和帮助.