Move語(yǔ)言安全性分析及合約審計(jì)要點(diǎn)之邏輯校驗(yàn)漏洞
1、邏輯校驗(yàn)漏洞
智能合約開(kāi)發(fā)的業(yè)務(wù)相關(guān)邏輯設(shè)計(jì)復(fù)雜,涉及的經(jīng)濟(jì)學(xué)計(jì)算和參數(shù)較多,不同項(xiàng)目和協(xié)議之間可組合性極其豐富,很難預(yù)測(cè),非常容易出現(xiàn)安全漏洞。
在Solidity智能合約中,我們總結(jié)了4種類型的邏輯校驗(yàn)漏洞:
(1)未校驗(yàn)返回值
(2)未校驗(yàn)相關(guān)計(jì)算數(shù)據(jù)公式
(3)未校驗(yàn)函數(shù)參數(shù)
(4)未規(guī)范使用require校驗(yàn)
同樣地,我們將從這4個(gè)方面分析Move合約中是否存在這些邏輯檢驗(yàn)漏洞以及其可能性和危害。
1.1 未校驗(yàn)返回值
不檢查消息調(diào)用的返回值,即使被調(diào)用的函數(shù)返回一個(gè)異常值,執(zhí)行邏輯仍然會(huì)繼續(xù)進(jìn)行,只是該函數(shù)的調(diào)用并沒(méi)有實(shí)現(xiàn)正確的邏輯,這會(huì)導(dǎo)致整個(gè)交易得不到正確的結(jié)果,甚至?xí){到數(shù)字資產(chǎn)的安全性。
比如,Solidity合約中的call函數(shù),functionCallWithValue函數(shù)如下:
代碼中調(diào)用了call函數(shù),如果call函數(shù)執(zhí)行發(fā)生意外,比如轉(zhuǎn)賬失敗,則返回值success為false。如果沒(méi)有驗(yàn)證該返回值,即使success為false,交易仍然會(huì)正常執(zhí)行。只是交易中的這筆轉(zhuǎn)賬沒(méi)有成功。這里通過(guò)require對(duì)success進(jìn)行了驗(yàn)證,如果是false,交易就會(huì)回滾(revert)。
call函數(shù)是Solidity動(dòng)態(tài)函數(shù)調(diào)用的一個(gè)關(guān)鍵函數(shù),是Solidity語(yǔ)言層面的一個(gè)容易因?yàn)榉祷刂刀a(chǎn)生漏洞的典型代表。除了call函數(shù)之外,在業(yè)務(wù)層面,Solidity合約也經(jīng)常使用返回值來(lái)判斷函數(shù)是否執(zhí)行成功,比如ERC20合約中的函數(shù):
對(duì)于這類函數(shù),在實(shí)際應(yīng)用的時(shí)候一般需要對(duì)返回值進(jìn)行校驗(yàn),否則會(huì)產(chǎn)生漏洞,甚至?xí){到數(shù)字資產(chǎn)的安全性。
此外,根據(jù)實(shí)際的業(yè)務(wù)邏輯,函數(shù)會(huì)返回一些業(yè)務(wù)需要的數(shù)據(jù),這些數(shù)據(jù)也需要根據(jù)業(yè)務(wù)進(jìn)行驗(yàn)證,進(jìn)一步保證函數(shù)調(diào)用沒(méi)有發(fā)生意外,包括但不限于返回值的類型、長(zhǎng)度、范圍等。比如上面的functionCallWithValue函數(shù)中,調(diào)動(dòng)了verifyCallResultFromTarget函數(shù)對(duì)返回值進(jìn)行校驗(yàn)。其不僅對(duì)返回值success進(jìn)行了檢查,還對(duì)retrundata的長(zhǎng)度進(jìn)行了校驗(yàn)和處理。
在Move合約中,從語(yǔ)言層面來(lái)講,由于其靜態(tài)調(diào)用的特性,不存在類似于Solidity中的call函數(shù)需要校驗(yàn)返回值的情況,即使有需要校驗(yàn)函數(shù)是否執(zhí)行正確,一般會(huì)在spec模塊使用規(guī)范語(yǔ)言在Move Prover中進(jìn)行校驗(yàn),校驗(yàn)失敗則交易會(huì)中止。
從業(yè)務(wù)層面來(lái)講,Move合約中的spec模塊同樣可以校驗(yàn)函數(shù)對(duì)全局?jǐn)?shù)據(jù)的修改。此外,還可以在合約中編寫(xiě)單元測(cè)試函數(shù)對(duì)函數(shù)直接進(jìn)行單元測(cè)試,來(lái)保證函數(shù)執(zhí)行的正確性。因此,一般不會(huì)將表示函數(shù)執(zhí)行是否成功的布爾變量作為返回值。因此,Move函數(shù)的返回值多是實(shí)際的業(yè)務(wù)數(shù)據(jù),是否需要校驗(yàn),則需要根據(jù)實(shí)際業(yè)務(wù)需求來(lái)確定,比如需要根據(jù)返回值的不同,進(jìn)入不同的函數(shù)邏輯分支,則需要對(duì)返回值進(jìn)行判定和檢驗(yàn),比如DEX中的流動(dòng)性函數(shù):
X與Y的排序不同,需要訪問(wèn)的balance也是不同的,還需要校驗(yàn)order!=0。
總的來(lái)說(shuō),Move語(yǔ)言靜態(tài)調(diào)用特性、spec模塊以及單元測(cè)試等極大地提高了函數(shù)的安全性,這一點(diǎn)Solidity要好很多。但也不排除函數(shù)會(huì)因?yàn)闆](méi)有校驗(yàn)返回值而產(chǎn)生漏洞的情況。因此,開(kāi)發(fā)人員更需要對(duì)業(yè)務(wù)和實(shí)現(xiàn)邏輯熟悉,開(kāi)發(fā)的時(shí)候需要謹(jǐn)慎而行。
1.2 未校驗(yàn)相關(guān)計(jì)算數(shù)據(jù)
相關(guān)業(yè)務(wù)在合約實(shí)現(xiàn)過(guò)程中,考慮到情況不夠全面沒(méi)有正確校驗(yàn)相應(yīng)的業(yè)務(wù)經(jīng)濟(jì)學(xué)公式和計(jì)算數(shù)據(jù),導(dǎo)致合約對(duì)于特殊的計(jì)算數(shù)據(jù)容錯(cuò)性差。比如:
(1)XCarnival安全事件
事件發(fā)生在2022年6月24日,NFT借貸協(xié)議XCarnival遭受到黑客攻擊,損失大約380萬(wàn)美元。
根本原因是controller合約borrowAllowed函數(shù)調(diào)用的orderAllowed函數(shù)對(duì)數(shù)據(jù)結(jié)構(gòu)order的校驗(yàn)不完整,僅僅是校驗(yàn)了訂單存在、地址正確并且沒(méi)有被清算,并沒(méi)有校驗(yàn)訂單中的NFT是否被提取,即使訂單中的NFT已經(jīng)被提取了,order的校驗(yàn)仍然可以通過(guò)。
(2)Fortress Loans安全事件
事件發(fā)生在2022年5月9日,F(xiàn)ortress Loans遭到黑客攻擊,損失了1048.1 ETH以及40萬(wàn)DAI。
根本原因是submit函數(shù)雖然校驗(yàn)了signer的數(shù)量,但卻沒(méi)有對(duì)signer本身和計(jì)算的數(shù)據(jù)power進(jìn)行校驗(yàn)。
這使得攻擊者可以調(diào)用submit函數(shù)修改狀態(tài)變量fcds,最終修改了價(jià)格預(yù)言機(jī)中的價(jià)格。
最終,攻擊者利用該漏洞竊取了1048.1 ETH以及40萬(wàn)DAI。
類似的安全事件還有不少,它們都是因?yàn)樵诤瘮?shù)內(nèi)部缺少對(duì)經(jīng)濟(jì)模型建立的數(shù)據(jù)結(jié)構(gòu)或者計(jì)算的數(shù)據(jù)缺少校驗(yàn)引起的漏洞。這類漏洞是由于項(xiàng)目設(shè)計(jì)與開(kāi)發(fā)并沒(méi)有考慮到全部的情況造成的,其嚴(yán)重等級(jí)不一,嚴(yán)重的甚至?xí)o項(xiàng)目帶來(lái)極大的經(jīng)濟(jì)損失,就像上面的安全事件。
在Move合約實(shí)現(xiàn)各類項(xiàng)目時(shí),同樣難以保證不會(huì)出現(xiàn)這類問(wèn)題,尤其是新型項(xiàng)目。希望發(fā)生在Solidity智能合約中的這些安全事件能夠給Move開(kāi)發(fā)者一些警示,在開(kāi)發(fā)過(guò)程中,盡可能地避免安全漏洞。
1.3 未校驗(yàn)函數(shù)參數(shù)
函數(shù)接收參數(shù)時(shí),它不會(huì)自動(dòng)地驗(yàn)證輸入的數(shù)據(jù)屬性是否具有安全性和正確性。因此,函數(shù)在實(shí)現(xiàn)的時(shí)候需要根據(jù)業(yè)務(wù)需要對(duì)參數(shù)進(jìn)行校驗(yàn),若缺少校驗(yàn)后者校驗(yàn)不符合業(yè)務(wù)需求,則會(huì)產(chǎn)生漏洞,甚至?xí){到數(shù)字資產(chǎn)的安全性。
以Superfluid.Finance安全事件為例。事件發(fā)生在2022年2月8日,以太坊上的DeFi協(xié)議Superfluid遭遇黑客攻擊,損失超1300萬(wàn)美元。
根本原因在于,Superfluid合約存在嚴(yán)重的邏輯漏洞,callAgreement函數(shù)缺少對(duì)參數(shù)的校驗(yàn),使得攻擊者將合約構(gòu)造的ctx數(shù)據(jù)替換為自定義ctx數(shù)據(jù),這給攻擊者發(fā)起攻擊提供了機(jī)會(huì)。
在Move合約開(kāi)發(fā)中更加需要對(duì)參數(shù)進(jìn)行校驗(yàn)。在Move中,函數(shù)的參數(shù)不僅僅是業(yè)務(wù)需求的數(shù)據(jù),還包括了權(quán)限需要的數(shù)據(jù),比如signer。Move沒(méi)有類似Solidity中的msg.sender這種全局變量,Move中對(duì)權(quán)限的鑒定是通過(guò)參數(shù)實(shí)現(xiàn)的。比如下面的函數(shù):
該函數(shù)中的account參數(shù)是代幣鑄造的發(fā)起賬戶,它必須鑄幣的權(quán)限,即MintCapStore,類似于Solidity中的msg.sender必須是owner。如果缺失了這部分校驗(yàn),該代幣就是任何賬戶都可以鑄造的了。
此外,Move生態(tài)中的項(xiàng)目類型跟Solidity生態(tài)相同,只是實(shí)現(xiàn)的語(yǔ)言不同。因此,Solidity合約中存在的業(yè)務(wù)邏輯上的漏洞在Move合約中有很大的可能性依然存在。因此,Move開(kāi)發(fā)者在開(kāi)發(fā)項(xiàng)目時(shí)要注意這些在Solidity合約中已經(jīng)出現(xiàn)過(guò)的漏洞。
1.4 未規(guī)范使用require
Solidity中的require旨在驗(yàn)證函數(shù)的外部輸入,包括調(diào)用者輸入的參數(shù)、函數(shù)的返回值、函數(shù)執(zhí)行前后狀態(tài)變化等。如果不能規(guī)范使用require,合約可能會(huì)產(chǎn)生漏洞,甚至威脅到數(shù)字資產(chǎn)的安全性,比如XDXSwap安全事件。
事件發(fā)生在2021年7月2日,火幣生態(tài)鏈(Heco)上DeFi項(xiàng)目XDXSwap受到閃電貸攻擊,損失約400萬(wàn)美金。
根本原因就是閃電貸的功能實(shí)現(xiàn)合約,存在借出不還的嚴(yán)重漏洞,造成巨額損失,是項(xiàng)目方fork Uniswap合約代碼并修改時(shí)引入的嚴(yán)重漏洞,即缺少K值校驗(yàn)的require語(yǔ)句。最根本的原因還是業(yè)務(wù)的不熟悉,導(dǎo)致實(shí)現(xiàn)存在漏洞。
在Move合約中, assert語(yǔ)句和spec模塊完成require類似的功能。同樣,很多Solidity生態(tài)的項(xiàng)目,包括DEX、借貸、農(nóng)場(chǎng)等類型的項(xiàng)目在未來(lái)都將出現(xiàn)在Move的生態(tài)中。Move與Solidity原理以及機(jī)制是不同的,但項(xiàng)目的業(yè)務(wù)時(shí)相同的。鑒于Solidity生態(tài)項(xiàng)目踩坑無(wú)數(shù),安全事件層出不窮,Move雖然安全性高,但是在實(shí)現(xiàn)各類項(xiàng)目時(shí)仍然要謹(jǐn)慎小心,盡量避免出現(xiàn)同類型的漏洞,希望同一個(gè)坑不要再踩一次了。
2、總結(jié)
當(dāng)下Move仍處于發(fā)展階段,Move生態(tài)離成熟尚一定距離,開(kāi)發(fā)者較少,開(kāi)發(fā)者經(jīng)驗(yàn)欠缺,真正能夠熟練開(kāi)發(fā)Move合約的不多,因此更容易出現(xiàn)業(yè)務(wù)層面的一些漏洞。這需要Move合約在設(shè)計(jì)和開(kāi)發(fā)過(guò)程中對(duì)Move語(yǔ)言特性以及業(yè)務(wù)都要熟悉,才可能少出現(xiàn)業(yè)務(wù)漏洞。
另外,Solidity已經(jīng)實(shí)現(xiàn)了大量的業(yè)務(wù)類型,比如去中心化交易所、去中心化借貸、收益聚合、杠桿借貸、杠桿挖礦、閃電貸、跨鏈交易等。這些典型的業(yè)務(wù)場(chǎng)景需要在Move生態(tài)足逐一實(shí)現(xiàn),而且需要結(jié)合Move與Solidity的差異進(jìn)行重新設(shè)計(jì)實(shí)現(xiàn)方案。在這個(gè)過(guò)程中,就比較容易出現(xiàn)一下漏洞,就像Solidity早期經(jīng)歷了很多次攻擊和大量資產(chǎn)的損失才逐步走向成熟。Move雖然是一個(gè)安全性較高的語(yǔ)言,但誰(shuí)也無(wú)法保證沒(méi)有漏洞,我們希望可以借鑒Solidity的發(fā)展過(guò)程,讓Move生態(tài)的發(fā)展少走一些彎路,少一些損失,更快更穩(wěn)地走向成熟。