1.逻辑验证漏洞智能合约开发的业务相关逻辑设计复杂,涉及的经济计算和参数较多,不同项目和协议可能组合极其丰富,难以预测,极易出现安全漏洞。在Solidity智能合约中,我们总结了4种逻辑验证漏洞:(1)未验证的返回值(2)未验证的相关计算数据公式(3)未验证的函数参数(4)未规范的使用同样,我们将分析是否存在这些从这四个方面分析Move合约逻辑验证的漏洞,以及它们的可能性和危害。1.1未经检查的返回值消息调用的返回值未经检查。即使被调用的函数返回异常值,执行逻辑也会继续,但是函数调用没有执行正确的逻辑,就会导致整个交易失败。到正确的结果,甚至可能威胁到数字资产的安全。例如Solidity合约中的call函数,functionCallWithValue函数如下:在代码中调用call函数,如果call函数执行过程中出现意外,比如转账失败,则返回值success是假的。如果没有验证返回值,即使success为false,交易也会正常执行。只是这次交易中的转账没有成功。这里通过require验证是否成功,如果为false,事务将被回滚(revert)。call函数是Solidity动态函数调用的关键函数,是Solidity语言级别容易因返回值而出现漏洞的典型代表。除了调用函数,在业务层面,Solidity合约往往会通过返回值来判断函数是否执行成功,比如ERC20合约中的函数:对于这类函数,一般需要验证实际应用中的返回值。否则,就会出现漏洞,甚至威胁到数字资产的安全。另外,根据实际的业务逻辑,函数会返回一些业务需要的数据。这些数据还需要根据业务进行校验,进一步保证函数调用不会出现意外,包括但不限于返回值的类型、长度、范围等。比如上面的functionCallWithValue函数中,就调动了verifyCallResultFromTarget函数来校验返回值。它不仅检查返回值success,还会检查并处理retrundata的长度。在Move合约中,从语言层面来说,由于其静态调用的特点,不会出现类似Solidity中调用函数需要校验返回值的情况。即使需要验证功能是否正确执行,一般也是在spec模块中使用规范语言在MoveProver中进行验证。如果验证失败,交易将被中止。从业务角度看,Move合约中的spec模块也可以验证函数对全局数据的修改。另外,也可以在合约中编写单元测试函数,直接对函数进行单元测试,保证函数执行的正确性。因此,表示函数执行是否成功的布尔变量一般不作为返回值。因此,Move函数的返回值大多是实际的业务数据。是否需要验证需要根据实际业务需要来确定。比如需要根据返回值进入不同的函数逻辑分支,需要对返回值进行判断和校验,比如DEX中的流动性函数:X和Y的顺序不同,需要的余额被访问的也不一样,需要检查order!=0。总的来说,Move语言的静态调用特性、spec模块和单元测试,大大提高了函数的安全性,比Solidity好很多。但不排除该函数会因为返回值未校验而存在漏洞。因此,开发者需要更加熟悉业务和实现逻辑,开发时需要谨慎。1.2未经核对的相关计算数据在合同执行过程中,考虑到相关业务的全面性不足,未正确验证相应的商业经济学公式和计算数据,导致合同对特殊计算数据的容错性差。例如:(1)XCarnival安全事件事件发生在2022年6月24日,NFT借贷协议XCarnival被黑客攻击,损失约380万美元。根本原因是controller合约的borrowAllowed函数调用的orderAllowed函数在数据结构顺序的校验上不完整。它仅验证订单存在,地址正确且未被清算。不验证订单中的NFT是否已经提取,即使订单中的NFT已经提取,订单验证依然可以通过。(二)堡垒贷安全事件事件发生在2022年5月9日,堡垒贷被黑客攻击,损失1048.1ETH和400,000DAI。根本原因在于提交函数虽然验证了签名者的数量,但并没有验证签名者本身和计算出的数据算力。这使得攻击者可以调用提交函数修改状态变量fcds,最终修改价格预言机中的价格。最终,攻击者利用该漏洞盗取了1048.1ETH和400,000DAI。类似的安全事件还有很多,都是由于经济模型没有数据结构或者函数内部的计算数据没有校验造成的。此类漏洞是由于项目设计和开发没有考虑到所有情况造成的,其严重程度不一,严重的甚至可能给项目带来巨大的经济损失,就像上面的安全事件一样。Move合约在执行各种项目时,也很难保证不会出现此类问题,尤其是新项目。希望Solidity智能合约中发生的这些安全事件能够给Move开发者一些警示,在开发过程中尽量避免出现安全漏洞。1.3未经验证的函数参数函数接收参数时,不会自动验证输入的数据属性是否安全正确。因此,在实现功能时,需要根据业务需要对参数进行校验。如果验证缺失,后期验证不符合业务需求,就会造成漏洞,甚至威胁到数字资产的安全。以Superfluid.Finance安全事件为例。事件发生在2022年2月8日,以太坊上的DeFi协议Superfluid遭到黑客攻击,损失超过1300万美元。根本原因是Superfluid合约存在严重的逻辑漏洞。callAgreement函数缺少对参数的校验,使得攻击者可以将合约构造的ctx数据替换为自定义的ctx数据,为攻击者发动攻击提供了可乘之机。在Move合约的开发中,更需要对参数进行校验。在Move中,函数的参数不仅是业务需要的数据,还有权限需要的数据,比如signer。Move没有像Solidity中的msg.sender这样的全局变量。Move中权限的认证是通过参数来实现的。比如下面这个函数:这个函数中的account参数是代币铸造的发起账户,必须有铸币权限,即MintCapStore,类似Solidity中的msg.sender必须是owner。如果缺少这部分验证,则令牌可以由任何帐户铸造。此外,Move生态中的项目类型与Solidity生态中的项目类型相同,只是实现语言不同。因此,Move合约中极有可能存在Solidity合约的业务逻辑漏洞。因此,Move开发者在开发项目时要注意Solidity合约中出现的这些漏洞。1.4require的无规范使用Solidity中的require旨在验证函数的外部输入,包括调用者输入的参数、函数的返回值、函数执行前后的状态变化等。如果使用的需求无法标准化,合约可能存在漏洞,甚至威胁到数字资产的安全,如XDXSwap安全事件。事件发生在2021年7月2日,火币生态链(Heco)上的DeFi项目XDXSwap遭到闪贷攻击,损失约400万美元。根本原因是闪电贷功能实现合约,存在借贷不还的严重漏洞,造成巨大损失。这是项目方对Uniswap合约代码进行fork修改后引入的严重漏洞,即缺少K值校验的require语句。最根本的原因是对业务的不熟悉,导致执行上出现漏洞。在Move合约中,assert语句和spec模块执行类似于require的功能。同样,未来Move生态中也会出现很多Solidity生态项目,包括DEX、借贷、farm等类型的项目。Move和Solidity的原理和机制不同,但是项目的业务是一样的。鉴于Solidity生态项目陷阱众多,安全事件层出不穷,Move虽然安全性高,但在实施各类项目时仍需谨慎,尽量避免出现同类型漏洞。希望不要再踩同样的坑。2.综上所述,Move还处于开发阶段。Move生态距离成熟还有一定距离。开发人员很少,开发人员缺乏经验。真正精通Move合约开发的开发者并不多,因此更容易出现业务层面的一些漏洞。这就需要Move合约在设计和开发过程中熟悉Move语言特性和业务,这样才不会出现业务漏洞。此外,Solidity实现了大量的业务类型,如去中心化交易所、去中心化借贷、收益聚合、杠杆借贷、杠杆挖矿、闪电贷、跨链交易等,这些典型的业务场景需要通过一个Move生态系统中的一个,需要根据Move和Solidity的差异重新设计实施方案。在这个过程中,更容易出现漏洞,就像Solidity在逐渐成熟之前经历了早期的多次攻击和大量资产的损失一样。Move虽然是一种安全性很高的语言,但谁也不能保证没有漏洞。我们希望能够借鉴Solidity的发展历程,让Move生态的发展少走弯路,少走弯路,更快更稳健地走向成熟。
