导语:此博客文章介绍了由Blaze Information Security执行的智能合约的安全审核结果,并代表客户端Array.io(以前叫做Annihilat.io)公开这些结果。
介绍
此博客文章介绍了由Blaze Information Security执行的智能合约的安全审核结果,并代表客户端Array.io(以前叫做Annihilat.io)公开这些结果。本文的内容包含了2017年12月底发布的报告中的相同信息和结果。
审计由Victor Farias(项目负责人)和Blaze Information Security的Julio Fort执行。我们很高兴得知Array.io团队认真对待安全问题并聘请了三家不同的公司来审计他们的智能合约。
免责声明:本文介绍了审计范围内智能合约的安全审计结果。作为一个有时间限制的练习,它不能保证智能合约中没有其他安全问题。审计结果不应被视为投资建议。
报告
本文档介绍了Array.io智能合约安全审计的结果。此参与旨在验证智能合约是否仅执行其预期要做的事情,并发现在合约部署到区块链网络之前可能对Array.io ANNI令牌产生负面影响的安全漏洞。
Array.io是基于初始开发产品(IDO)概念的资金概念。从本质上讲,它是在IDO中筹集资金的一个项目的贡献者的工资令牌。Array.io使用基于以太坊ERC20的令牌,合约是用Solidity编写的。有关Array.io和IDO的详细信息,请参阅这份白皮书。
本次的安全分析侧重于发现与代码实现相关的漏洞以及由架构和设计错误所引起的问题,以及文档和代码之间的不一致。
对于不符合以太坊令牌标准或合约规范的每一个代码模式,以及评估期间发现的编码最佳实践与漏洞产生的偏差,Blaze Information Security 团队将对风险进行评级从而得出风险等级,并在有可能的情况下验证漏洞的存在并利用漏洞。
评估的主要目标如下:
· 确定智能合约中存在的主要安全相关问题
· 评估项目中存在的安全编码实践的级别
· 获取每个漏洞的证据,并在可能的情况下利用可用的漏洞
· 以清晰易复制的方式记录用于复制问题的所有程序
· 为分析中发现的每个缺陷推荐缓解因子和修复方案
· 基于现实威胁模型为实际风险情景提供背景信息
执行摘要
该审计活动在五个工作日内完成,包括撰写报告。智能合约安全审计于2017年12月12日开始,于2017年12月18日结束,完成了本报告的初步版本。
2017年12月21日,Blaze Information Security报告的所有审计结果均由Array.io做了相应修改。这些问题不再出现在合约代码中,并在提交b287f07393f8b5b67cbde5c1d70dfc31b9cd5aa1和afdf264d030e8313fd65e1c8c236d2a958eff7c7中得到修复。
审计是在自动化工具的帮助下完成的,并进行了人工审计。在此评估中未检查生成的EVM代码。
在此次约定中审计的合约中发现了三个问题。总体而言,范围内的合约包含重大漏洞,可能导致令牌丢失并对Array.io的运营产生重大影响。他们还缺乏防御性安全编码模式,还有其他非推荐的Solidity编程实践。
重要的是要注意这些漏洞不再存在,因为它们已被Array.io更正并且审计人员审计了修复程序。
审计范围
此安全审计的范围包括两个用Solidity编写的智能合约。
· 项目名称:annihilatio
· 提交:8ad0a1bdc7dff7e40ad5cf61aea89deaa982eab5
Token_flat.sol (345行)
Multisig_flat.sol(498行)
审计的代码是开源的,可以在https://github.com/annihilatio/ido/tree/8ad0a1bdc7dff7e40ad5cf61aea89deaa982eab5/smart-contracts找到
智能合约安全审计方法论
我们以安全为导向的智能合约审计遵循有组织的方法,旨在从有动力,技术能力和持久性的对手的角度确定合约范围内最大数量的漏洞。
特别关注智能合约的关键领域,例如令牌的燃烧和多签名的功能。我们的流程还研究了导致诸如可重入性,算术溢出和下溢,与燃料相关的拒绝服务等问题的其他常见实现问题。
Blaze的智能合约审计方法涉及自动和手动审计技术。使用诸如linters,程序分析器和源代码安全扫描程序之类的工具对应用程序进行一轮动态分析。
合约的源代码可以手动审计安全漏洞。这种类型的分析能够检测自动扫描器和静态分析器遗漏的问题,因为它可以发现边缘情况和业务逻辑相关的问题。
技术摘要
智能合约的描述
· Multisig钱包
为了安全地存储资金并平衡所有者之间的共识,array.io使用多签名钱包来控制令牌。此合约可用于更改令牌的配置,例如为投资者,创始人和项目的分享设置新值,呼叫TGE上线等。
· 令牌
该合约本身就是ANNI令牌。它包含与铸币,钱包中的令牌转移,钱币余额检查功能,令牌燃烧功能以及令牌规范中列出的其他功能相关的所有功能。
关于使用Multisig_flat.sol的意见
尝试在自动化工具中运行Multisig_flat.sol时出现了许多错误。Blaze Information Security建议开发人员查看合约代码,以了解大多数Solidity安全工具无法解析的原因。
漏洞描述
1.将ANNI token交易回ETH的不可能性将导致攻击者持有投资者的资金
· 已在afdf264d030e8313fd65e1c8c236d2a958eff7c7 提交中修复
· 严重性:严重
该合约具有令牌持有者将其ANNI令牌转换为以太(ETH)的函数。这个函数,称为burn(),通过首先将令牌中请求的数量转移到“燃烧地址”并随后将ETH转移给任何调用该函数的人来工作。
然而,在代码审查期间,Blaze Information Security注意到transfer()函数不允许转移到0x0地址,因此发送方的余额永远不会更新,并且由于缺少满足条件而发生异常require(),因此攻击者可以多次调用函数来消费令牌,但不会发生任何操作。
来自Token_Flat.sig第135行:
address constant public burnAddress = 0×0;
第217行:
function burn(uint _amount) public isNotTgeLive noAnyReentrancy returns(bool _success) { require(balances[msg.sender] >= _amount); transfer(burnAddress, amount); // here a transfer is supposed to happen to the burn address msg.sender.transfer(amount); Burn(msg.sender, _amount); return true; }
第59行:
function transfer(address _to, uint value) isNotFrozenOnly onlyPayloadSize(2 * 32) returns (bool success) { require(_to != address(0)); // the burn address is 0×0, require() will not be satisfied and will throw require(value <= balances[msg.sender]); // this line will never be executed
在给定的上述代码中,当调用burn()函数时,它将尝试执行像transfer(0×0,amount)和语句require( to!= address(0));但这样的转账操作不会按预期完成。这个问题的影响是非常严重的,因为持有ANNI Token的投资者永远无法将其Token转换回ETH。
· 解决方案
这个问题可能有不同的解决方案。Blaze建议不要在burn()中调用转账函数,而是验证金额并从帐户中扣除:
require(value <= balances[msg.sender]); balances[msg.sender] = balances[msg.sender].sub(value);
2.偏离合约的技术规范和清算token的代码
· 已在b287f07393f8b5b67cbde5c1d70dfc31b9cd5aa1 提交中修复
· 严重性:中等
根据智能合约的规范(https://github.com/annihilatio/ido/blob/master/SMART-CONTRACT-SPECS.md#burning):
令牌持有者可以随时清算令牌,在此阶段,令牌被烧毁,令牌合同将同样数量的ETH发送给令牌持有者。令牌持有者显然不能消耗比他所拥有的更多的令牌。同样,当标记被燃烧时,总供应也会减少相同数量的标记。
来自Token_flat.sol:
/// @dev Burn tokens to burnAddress from msg.sender wallet /// @param _amount Amount of tokens function burn(uint _amount) public isNotTgeLive noAnyReentrancy returns(bool _success) { require(balances[msg.sender] >= _amount); transfer(burnAddress, amount); msg.sender.transfer(amount); Burn(msg.sender, _amount); return true; }
根据上面的代码,令牌清算(消费事件)不能随时调用,与文档所说的相反,但仅限于TGE(令牌生成事件)不存在时。
审计小组了解此问题不会对合约本身带来任何负面的安全影响,但肯定会偏离智能合约技术规范中概述的预期功能。
· 解决方案
考虑从函数burn()中删除修饰符isNotTgeLive 。如果代码实际上反映了业务逻辑,则更改文档来准确反映该修饰符。
3.在某些函数的声明中缺少明确的可见性
· 已在b287f07393f8b5b67cbde5c1d70dfc31b9cd5aa1 提交中修复
· 严重性:低
审计发现,除了两个函数,finishTge()和mint(uint,uint,uint),都标记为内部,合约的所有其他函数都是公开的,因为其中一些尚未明确标记。其中许多函数的状态都是变化的。
默认情况下,Solidity将所有未标记的函数标记为公共,使其可由网络中的外部代理程序调用。为了限制此行为,开发人员应使用内部或私有标签来阻止它们从外部调用。
虽然Blaze Information Security注意到在函数上有不同的检查以防止外部各方调用它们的滥用,但具有不明确标记的函数被认为是一种糟糕的编程习惯,应该避免。此建议有望为开发团队采用,以审计函数的可见性并重新考虑其当前的标签。
审计合约的函数分析,包括每个函数的可见性状态,可以在本报告的附录B中找到
· 参考 https://consensys.github.io/smart-contract-best-practices/recommendations/
· 解决方案
添加函数和状态变量的显式可见性。
补充说明
· 在MultiSigWallet合约中,notNull() 修饰符可以从addTransaction(address destination, uint value, bytes data) 移动到函数submitTransaction() ,setLiveTx() 和setFinishedTx() 以在较早阶段验证它。
· 在MultiSigWallet函数中,isConfirmed() 不显式返回false。
· 在MultiSigWallet中的getSnsactionIds() 函数中的循环,可以使用变量'from'而不是0来初始化变量 'i' 来节省燃料。
· 在MultiSigWallet中,任何所有者可以在没有选举的情况下通过“setToken”函数更改“IToken令牌”变量吗?这似乎与多签名的目的背道而驰以及在钱包中执行操作的想法达成共识。
· 两个合约都以以下代码构造开头:
pragma solidity ^0.4.15;
根据https://consensys.github.io/smart-contractbest-practices/recommendations/#lock-pragmas-to-specific-compiler-version中列出的最佳实践,它应该是:
pragma solidity 0.4.15;
结论
安全评估的最终目标是提供修复建议,以更好地说明企业或组织的风险,并帮助其理解并验证其安全态势,以应对其业务的潜在威胁。
考虑到这一点,Blaze Information Security提供了以下建议,我们认为这些建议应作为进一步增强智能合约安全状况的后续措施:
· 解决报告中提出的所有问题,并考虑备注部分中的意见;
· 执行另一轮审计以验证修复;
· 考虑建立一个bug赏金计划,因为它在智能合约和区块链领域的公司中变得越来越普遍。
Blaze Information Security感谢Annihilat.io团队在整个活动期间给予的支持和帮助。我们真诚地希望在不久的将来再次与Array.io(Annihilat.io)合作。