時(shí)間:2021-12-17|瀏覽:323
無論是開發(fā)DeFi協(xié)議還是其他的智能合約應(yīng)用,在上線到區(qū)塊鏈主網(wǎng)前都需要考慮到許多安全因素。很多團(tuán)隊(duì)在審核代碼時(shí)只關(guān)注Solidity相關(guān)的陷阱,但要確保dApp的安全性足夠支撐上線主網(wǎng),通常還有很多工作要做。了解大多數(shù)流行的DeFi安全漏洞可能會(huì)為你和你的用戶節(jié)省數(shù)十億美元并且免除后續(xù)的各種煩惱,如預(yù)言機(jī)攻擊、暴力攻擊和許多其他威脅等。
考慮到這一點(diǎn),我們將在下文研究有關(guān)DeFi安全的十大最佳實(shí)踐,這將有助于防止你的應(yīng)用程序成為攻擊的受害者、避免與用戶的不愉快對(duì)話,并能保護(hù)和加強(qiáng)你作為一個(gè)超級(jí)安全的開發(fā)者的聲譽(yù)。
1 了解重入攻擊
一種常見的DeFi安全攻擊類型是重入攻擊,這也是臭名昭著DAO攻擊的形式。這種情況就是當(dāng)一個(gè)合約在更新自己的狀態(tài)之前調(diào)用了一個(gè)外部合約。
引用Solidity文檔的內(nèi)容:
"一個(gè)合約(A)與另一個(gè)合約(B)的任何交互,以及任何ETH的轉(zhuǎn)賬都會(huì)將控制權(quán)移交給該合約(B)。這使得B有可能在這個(gè)交互完成前回調(diào)到A。"
我們來看看一個(gè)例子:
在這個(gè)函數(shù)中,我們用msg.sender.call調(diào)用另一個(gè)賬戶。我們要記住的是,這可能是另一個(gè)智能合約!
在(bool success,) = msg.sender.call{value: shares[msg.sender]}(""); 返回之前,被調(diào)用的外部合約可以被編碼為再次調(diào)用withdraw(提款)函數(shù)。這將允許用戶在狀態(tài)更新前提取合約中的所有資金。
合約可以有幾個(gè)特殊函數(shù),即receive(接收)和fallback(回退)函數(shù)。如果你發(fā)送ETH到另一個(gè)合約,它將自動(dòng)被路由到receive函數(shù)。如果該receive(接收)函數(shù)再指向原來的合約,那么在你有機(jī)會(huì)將余額更新為0之前,你就可以不斷提款。
讓我們看看這種合約可能是什么樣子的:
在這個(gè)函數(shù)中,當(dāng)你把ETH發(fā)送到steal合約后,它將調(diào)用receive函數(shù),該函數(shù)指向Fund合約。此時(shí),我們還沒有運(yùn)行shares[msg.sender] = 0,所以合約仍然認(rèn)為用戶有可以提取的余額。
解決方案:在轉(zhuǎn)移ETH/通證或調(diào)用不受信任的外部合約之前,更新合約的內(nèi)部狀態(tài)
有幾種方法可以做到這一點(diǎn),從使用互斥鎖到甚至簡(jiǎn)單地排序你的函數(shù)調(diào)用,你只在狀態(tài)被更新后才能接觸到外部合約或函數(shù)。一種簡(jiǎn)單的修復(fù)方法是在調(diào)用任何外部未知合約之前更新狀態(tài):
轉(zhuǎn)移、調(diào)用和發(fā)送
長(zhǎng)期以來,Solidity安全專家建議不要使用上述方法。他們建議不使用call函數(shù),而是使用transfer,像下面這樣:
sKLBmMBmPY1PDJAraHaB4A1IBJCXhBbDuvTb5A6n.png
我們之所以提到這一點(diǎn),是因?yàn)槟憧赡軙?huì)看到外面有一些相互矛盾的資料,它們的建議與我們的建議相反。此外,你也會(huì)聽到send函數(shù)。每一個(gè)函數(shù)都可以用來發(fā)送ETH,但都有輕微的差異。
transfer: 最多需要2300個(gè)gas,失敗時(shí)會(huì)拋出一個(gè)錯(cuò)誤
send: 最多需要2300個(gè)gas,失敗時(shí)返回false
call: 將所有g(shù)as轉(zhuǎn)移到下一個(gè)合約,失敗時(shí)返回false
transfer和send在很長(zhǎng)一段時(shí)間內(nèi)被認(rèn)為是 "更好 "的做法,因?yàn)?300個(gè)gas真的只夠發(fā)出一個(gè)事件或其他無害的操作;接收合約除了發(fā)出事件不能回調(diào)或做任何惡意操作,因?yàn)槿绻麄儑L試這樣做的話,他們會(huì)耗盡gas。
然而,這只是目前的設(shè)置,由于不斷變化的基礎(chǔ)設(shè)施生態(tài),gas成本在未來可能會(huì)發(fā)生變化。我們已經(jīng)看到有EIP改變了不同操作碼的gas成本。這意味著未來可能有一段時(shí)間,你可以以低于2300個(gè)gas的價(jià)格調(diào)用一個(gè)函數(shù),或者事件的成本將超過2300個(gè)gas,這意味著任何現(xiàn)在要發(fā)出事件的接收函數(shù)會(huì)在未來會(huì)失敗。
這意味著最好的做法是在調(diào)用項(xiàng)目外的任何合約之前更新狀態(tài)。另一個(gè)可能的緩解措施是對(duì)關(guān)鍵函數(shù)施加一個(gè)互斥鎖,例如ReentrancyGuard中的非重入修改器。采用這樣的互斥鎖將阻止交易合約被重入。這實(shí)質(zhì)上是增加了一個(gè)“鎖”,所以在合約執(zhí)行過程中,任何調(diào)用合約的人都不能“重新進(jìn)入”該合約。
重入攻擊的另一個(gè)版本是跨函數(shù)重入。下面是一個(gè)跨函數(shù)重入攻擊的例子,為了便于閱讀,使用了transfer函數(shù):
有可能在另一個(gè)函數(shù)完成之前調(diào)用一個(gè)函數(shù)。這應(yīng)該是一個(gè)明確的提醒,在你發(fā)送ETH之前一定要先更新狀態(tài)。一些協(xié)議甚至在他們的函數(shù)上添加了互斥鎖,這樣如果另一個(gè)函數(shù)還沒有返回,這些函數(shù)就不能被調(diào)用。
除了常見的重入漏洞外,還有一些重入攻擊可以由特定的EIP機(jī)制觸發(fā),如ERC777。ERC-777(EIP-777)是建立在ERC-20(EIP-20)之上的以太坊代幣標(biāo)準(zhǔn)。它向后兼容ERC-20并增加了一個(gè)功能,使“運(yùn)營(yíng)商”能夠代表通證所有者發(fā)送通證。關(guān)鍵是該協(xié)議還允許為通證所有者添加“send/receive鉤子”,以便在發(fā)送/接收交易時(shí)自動(dòng)采取進(jìn)一步行動(dòng)。
從Uniswap imBTC黑客事件中可以看出,該漏洞實(shí)際上是由Uniswap交易所在余額變化之前發(fā)送ETH造成的。在那次攻擊中,Uniswap功能的實(shí)現(xiàn)沒有遵循已被廣泛采用的“Check-Effect-Interact”模式,該模式是為了保護(hù)智能合約免受重入攻擊而發(fā)明的,按照該模式,通證轉(zhuǎn)移應(yīng)該在任何ETH轉(zhuǎn)移之前進(jìn)行。