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