智能合約中常見漏洞和攻擊的介紹
什麼是智能合約?
以太坊有兩種常見的帳戶類型:外部擁有帳戶(EOA)和智能合約帳戶(SCA)。
EOA 與我們常用於存儲資金和與應用程式互動的電子金融帳戶非常相似。例如,用戶通過 PayPal 存入法定貨幣,並與各種網站、商店和應用程式進行支付互動。DeFi 礦工通常將加密貨幣存儲在他們的 EOA 中,與 DeFi dApps 互動,並將資金存入 dApps 以獲取利潤。然而,EOA 擁有電子金融帳戶所沒有的一個特點:用戶必須通過擁有私鑰來驗證對 EOA 的控制權——不是你的密鑰,就不是你的幣。
SCA 也是一種帳戶類型,本質上與一段可執行的字節碼(也稱為智能合約)相關聯。智能合約描述了各種業務邏輯,並作為 dApps 的後端。然而,儘管與傳統的圖靈完備開發語言相比有更多限制,準圖靈完備的智能合約仍然容易受到許多攻擊,給區塊鏈行業帶來了無數打擊。
常見的智能合約攻擊
1. 重入攻擊
最常見和惡名昭彰的攻擊是重入攻擊,這種攻擊導致了以太坊分叉,進而產生了以太坊經典。2016年,黑客對 The DAO 合約執行了重入攻擊,竊取了 3,600,000 ETH,當時價值超過 1.5 億美元。這次攻擊發生在以太坊的早期階段,對生態系統造成了嚴重破壞,動搖了投資者的信心,最終導致了分叉。
具體邏輯
這裡有一個例子可以幫助您更好地理解重入攻擊的原理。銀行 B 之前借了一些錢給銀行 A。有一天,銀行 B 發起轉賬給銀行 A,要求將所有錢轉回銀行 B。正常路徑如下:
步驟 1:銀行 B 請求提取資金
步驟 2:銀行 A 將資金轉給銀行 B
步驟 3:銀行 A 確認成功轉賬給銀行 B
步驟4:銀行A更新銀行B的賬戶餘額。
然而,如果銀行B在步驟2之後創建一個漏洞,並在步驟3沒有確認的情況下繼續要求銀行A的所有資金,那麼銀行B在銀行A的賬戶餘額將保持不變。這種遞歸調用將耗盡銀行A的所有資產。
相關智能合約
銀行A的合約包括兩個函數:
- deposit():一個存款函數,用於將資金存入銀行A並更新用戶的餘額;
- withdraw():一個提款函數,允許用戶從銀行A提取所有資金。
- 銀行B的攻擊合約主要涉及一個觸發receive()回調函數的循環,該函數進而調用Bank合約的withdraw()函數,通過1次存款、1次提款和receive()回調函數調用的序列來耗盡銀行A的資產,最後更新B在A中的餘額。它包括兩個函數:receive():一個在收到ETH時觸發的回調函數,遞歸調用Bank合約的withdraw()函數進行提款。
- attack():首先調用Bank合約的deposit()函數刷新餘額,然後調用withdraw()函數發起第一次提款,並觸發receive()回調函數遞歸調用withdraw()以耗盡Bank合約的資產。
解決方案
實施重入鎖
重入鎖是一種用於防止重入的修飾符,確保在一次調用完成執行之前不能再次被調用。例如,由於 Bank B 的攻擊需要多次調用 Bank 合約的 withdraw() 函數,實施重入鎖後這種攻擊將會失敗。
如何使用
2. tx.origin 的誤用
智能合約中tx.origin的主要功能是檢索發起交易的原始帳戶。在此,我們將討論智能合約中兩個常見的變量:msg.sender和tx.origin。msg.sender檢索直接調用智能合約的帳戶,而在區塊鏈世界中,由於不同智能合約的嵌套和相互調用(例如DeFi Lego),需要tx.origin來獲取發起交易的原始帳戶。當dApp開發者在代碼中僅驗證tx.origin的安全性,忽略攻擊者部署中間合約以繞過tx.origin並發起攻擊的安全驗證時,就會產生漏洞。
具體邏輯
這裡有一個例子,讓您深入了解常見的攻擊場景。比爾擁有一個智能錢包,該錢包驗證比爾是否為轉賬的發起人。有一次,比爾在一個釣魚網站上鑄造了一個NFT。這使得該網站能夠獲取比爾的身份,並使用他的身份從他的智能錢包發起轉賬,導致資產損失。在正常情況下,用戶不太可能落入這種陷阱,但在使用錢包與dApp互動時,他們經常忘記檢查互動提示。例如,如果兩者都涉及Mint()函數,粗心的用戶可能很容易落入釣魚陷阱。釣魚網站內的業務邏輯充滿了陷阱,所以在日常互動中檢查互動提示是否有錯誤很重要。
智能錢包合約
智能錢包合約包含一個函數:
- transfer():一個提款函數,只能由錢包所有者(在本例中是比爾)發起。
釣魚攻擊合約
在釣魚攻擊合約中,Mint() 誘使用戶將資金轉移到駭客的地址。它包含一個函數:
- Mint():一旦被調用,釣魚函數內部會執行 Wallet 合約的 transfer()。由於原始發起人是用戶(在本例中是 Bill)自己,因此 驗證 require(tx.origin == owner, "Not owner") 不會有問題。然而,轉賬的目標地址已被篡改為駭客的地址,導致資金被盜。
解決方案
1. 使用 msg.sender 而非 tx.origin
無論涉及多少合約調用(合約 A → 合約 B →...→ 目標合約),只驗證 msg.sender,即直接呼叫者,以避免惡意中間合約造成的攻擊。
2. 驗證 tx.origin == msg.sender
此方法可以阻止惡意合約,但開發者需要考慮自身業務實際情況,因為它實際上隔離了所有其他外部合約調用。
3. 隨機數生成器(RNG)攻擊
這要回溯到 2018 年和 2019 年左右的賭博或投注 dApp 趨勢。通常,開發者會在智能合約中使用某些種子來生成隨機數以在抽獎期間選出獲勝者。常見的種子包括 block.number、block.timestamp、blockhash 和 keccak256。然而,礦工可以完全控制這些種子,因此在某些情況下,惡意礦工可能會操縱這些變量以獲取利益。
常見的骰子合約
骰子合約包含一個函數:
- Bet():一個投注函數,用戶輸入投注數字並支付一定數量的 ETH。使用多個種子生成隨機數,如果投注數字與隨機數匹配,用戶就贏得整個獎池。
礦工的攻擊合約
只要礦工預先計算出獲勝的隨機數並在同一區塊中執行,就可以獲勝。這包括一個函數:
- attack():一個投注攻擊函數,礦工預先計算出獲勝的隨機數。由於它在同一區塊中執行,blockhash(block.number - 1) 和 block.timestamp 在同一區塊中是相同的。然後礦工調用骰子合約的 Bet() 函數來完成攻擊。
解決方案
使用預言機項目提供的鏈下隨機數
通過諸如 Chainlink 等預言機項目提供的服務,鏈上隨機數被注入鏈上合約以確保隨機性和安全性。然而,預言機項目也存在中心化風險,因此需要更成熟的預言機服務。
4. 重放攻擊
重放攻擊涉及使用先前使用過的簽名重新發起交易以竊取資金。近年來最著名的重放攻擊之一是在 Optimism 上對做市商 Wintermute 的 2000 萬 $OP 代幣的盜竊,這是一次跨鏈重放攻擊。由於 Wintermute 的多重簽名錢包帳戶僅暫時部署在以太坊主網上,駭客利用 Wintermute 在以太坊上部署多重簽名地址的交易簽名,在 Optimism 鏈上重新執行相同的交易,從而獲得了 Optimism 上多重簽名錢包帳戶的控制權。多重簽名錢包帳戶本質上是一個智能合約帳戶,這也展示了 SCA 和 EOA 之間的顯著差異。對於 EOA,普通用戶只需一個私鑰就可以控制以太坊和 EVM 兼容鏈上的所有地址(地址字符串完全相同),而 SCA 在部署後只在一條鏈上有效。
具體邏輯
在此,我們提供一個典型的重放攻擊(同鏈重放攻擊)示例。比爾擁有一個智能錢包,每次交易執行前都需要輸入他的電子簽名。現在,駭客露西已經竊取了比爾的電子簽名,她可以發起無限次交易以掏空比爾的智能錢包。
示例
一個具有漏洞的合約包含三個函數:
- checkSig():ECDSA 驗證函數,確保驗證結果為原先設置的簽名者。
- getMsgHash():生成哈希的函數,將 to 和 amount 組合形成哈希。
- transfer():轉賬函數,允許用戶從流動性池中提取資金。由於缺乏對簽名的限制,相同的簽名可以被重複使用,使駭客能夠持續盜取資金。
解決方案
在簽名組合中包含隨機數以防止重放攻擊。該參數的原理如下:
- 隨機數:它描述了區塊鏈網絡中外部擁有賬戶(EOA)交易次數的變量。它具有順序性和唯一性。每增加一次交易,隨機數值就會增加1。區塊鏈網絡將檢查交易的隨機數是否與賬戶當前的隨機數一致。因此,如果黑客使用已使用過的簽名,由於簽名組合中的隨機數值小於EOA當前的隨機數值,攻擊將會失敗。
5. 拒絕服務(DoS)攻擊
拒絕服務(DoS)攻擊在傳統Web2世界中並不新鮮。它指的是對服務器的任何干擾,如發送大量垃圾或破壞性信息,從而阻礙或完全破壞可用性。同樣,智能合約也受到此類攻擊的困擾,這些攻擊本質上旨在使智能合約無法正常運作。
具體邏輯
讓我們看一個例子。A項目正在進行協議代幣的公開發售,所有用戶都可以向流動性池(智能合約)貢獻資金以先到先得的方式購買配額,多餘的資金將返還給參與者。黑客Alice利用攻擊合約參與公開發售。一旦流動性池試圖將資金返還給Alice的攻擊合約,就會觸發DoS攻擊,導致返還操作永遠無法實現。結果,大量資金被鎖定在智能合約中。
公募合約實例
Example
公募合約包含兩個功能:
- deposit():存款功能,記錄存款人的地址和貢獻金額。
- refund():退款功能,項目團隊通過此功能將資金返還給投資者。
DoS 攻擊合約
DoS 攻擊合約包含一個功能:
- attack():儘管是攻擊功能,但本身並無問題。主要問題在於 Hacker 合約內置的 receive() 付款回調函數,其中包含了異常判斷。任何外部合約向 Hacker 合約轉賬都會通過 revert() 觸發異常,從而阻止操作完成。
解決方案
1.避免在調用外部合約時關鍵功能被卡住
從上述 PublicSale 合約的 refund() 函數中移除 require(success, "Refund Fail!");確保即使單個地址退款失敗,退款操作仍可繼續進行。
2. 解耦
在上述 PublicSale 合約的 refund() 函數中,允許用戶自行申請退款,而不是分發退款,從而最大限度地減少與外部合約的不必要互動。
6. permit 攻擊
在許可攻擊中,帳戶A預先向指定方提供簽名,然後帳戶B在獲得簽名後,可以執行授權的代幣轉移以竊取一定數量的代幣。此處,我們主要討論智能合約中兩種常見的代幣授權功能:approve()和permit()。
在常見的ERC20合約中,帳戶A可以調用approve()來為帳戶B授權一定數量的代幣,使後者能夠從前者處轉移這些代幣。此外,EIP-2612將permit()引入ERC20合約,而Uniswap於2022年11月發布了一種新的代幣授權標準Permit2。
具體邏輯
這裡舉個例子。有一天,比爾正在瀏覽一個區塊鏈新聞網站,突然出現了一個Metamask簽名彈窗。由於許多區塊鏈網站或應用程序使用簽名來驗證用戶登錄,比爾並未多想就直接完成了簽名。五分鐘後,他的Metamask資產被清空。比爾隨後在區塊鏈瀏覽器中發現,一個未知地址發起了一個permit()交易,緊接著是一個transferFrom()交易,清空了他的錢包。
示例
這兩個功能如下:
- approve():標準授權功能,帳戶A授權一定數量的資金給帳戶B。
- permit():簽名授權功能,帳戶B提交並完成簽名驗證以獲得帳戶A的授權金額。參數包括授權的擁有者、被授權者、授權金額、簽名截止時間,以及擁有者的簽名數據v、r和s。
解決方案
1. 注意鏈上交互中的每個簽名
儘管一些錢包採取了解碼和顯示approve()授權簽名信息的措施,但它們幾乎不會對permit()簽名釣魚提供警告,從而增加了攻擊風險。因此,強烈建議嚴格檢查每個未知簽名,以確保它是否針對permit()函數。
2. 將日常交互的錢包與存儲資產的錢包分開
這對加密貨幣用戶來說極為重要,尤其是對於空投獵人而言,因為他們每天都與無數的去中心化應用程序或網站進行交互,容易陷入陷阱。在用於日常交互的錢包中只存儲少量資金,可以將損失控制在可管理的範圍內。
7. 蜜罐攻擊
在區塊鏈行業,蜜罐攻擊指的是項目團隊部署的一種惡意代幣合約。該合約只授予項目團隊出售權限,而普通用戶只能買入而不能賣出,從而遭受損失。
具體邏輯
這裡有一個例子。在Telegram的公告中,項目A通知用戶代幣已在主網上部署並可以進行交易。由於該代幣只能買入而不能賣出,價格一開始持續上漲,害怕錯過機會的用戶不斷買入。一段時間後,當用戶發現無法賣出時,項目團隊抓住機會拋售代幣,導致價格暴跌。
示例
核心功能:
- _beforeTokenTransfer():一個在代幣轉移過程中被調用的內部函數,只有在所有者調用時才能成功;其他帳戶的調用將失敗。
解決方案
使用安全掃描工具
a. 對以太坊代幣使用 Token Sniffer
b. 對其他鏈上的代幣使用 Ave Check
c. 使用內建檢測工具的市場網站,如 Dextools
避免交易得分較低的代幣。
8. 搶先交易攻擊
搶先交易最初出現在傳統金融市場,資訊不對稱使金融中介機構能夠根據特定行業資訊迅速採取行動以獲取利潤。在區塊鏈行業,搶先交易主要源於鏈上搶先交易,即通過操縱礦工優先將自己的交易打包上鏈以獲取利潤。
在區塊鏈領域,礦工可以通過操縱他們打包進區塊的交易來獲利,例如排除某些交易和重新排序交易。這種利潤可以用礦工可提取價值(MEV)來衡量。在用戶的交易被添加到以太坊主網之前,大多數交易都會聚集在記憶池中。礦工在這個記憶池中搜索具有更高燃料費的交易,並優先打包這些交易以最大化他們的收益。通常,具有更高燃料費的交易更容易被礦工打包。同時,一些 MEV 機器人也會在記憶池中搜尋具有盈利能力的交易。
具體邏輯
以下是一個例子。比爾發現了一個價格波動較大的新熱門代幣。為了確保在 Uniswap 上成功進行代幣交易,比爾設置了一個異常寬鬆的滑點範圍。不幸的是,愛麗絲的 MEV 機器人在記憶池中檢測到這筆交易,並迅速提高燃料費,在比爾之前發起一筆買入交易,並在比爾的交易之後在同一個區塊內插入一筆賣出交易。在區塊確認後,這導致比爾遭受重大滑點損失,而愛麗絲則從低買高賣的套利操作中獲利。
示例
該功能如下所示:
- solve():一個猜謎函數,任何人都可以提交答案,如果提交的答案與目標答案匹配,提交者可以獲得10個以太幣。
- 過程:比爾找到了正確答案。
- 愛麗絲監控內存池,等待有人提交正確答案。
- 比爾調用solve()提交答案,並將燃氣價格設置為100 Gwei。
- 愛麗絲看到比爾發送的交易並發現了答案。她設置了比比爾更高的燃氣價格200 Gwei並調用solve()。
- 愛麗絲的交易在比爾之前被礦工打包。
- 愛麗絲贏得了10個以太幣的獎勵。
解決方案
三個主要功能如下:
- commitSolution():提交結果的函數,將用戶提交的答案solutionHash、提交時間commitTime和狀態revealed放入Commit結構中。
- getMySolution():獲取結果的函數,允許用戶查看他們提交的答案和相關信息,包括用戶提交的答案solutionHash、提交時間commitTime和狀態revealed。
- revealSolution():猜謎獎勵領取函數,允許用戶在提供答案和他們設置的密碼後領取獎勵。
流程:
- Bill 找到正確答案。
- Bill 呼叫 commitSolution() 提交正確答案。
- 在下一個區塊中,Bill 呼叫 revealSolution(),提供答案和他設定的密碼以領取獎勵。
在 commitSolution() 中,Bill 提交一個加密字串,只有他自己知道提交的明文數據。在此步驟中,還記錄了提交區塊時間 commitTime。接下來,在 revealSolution() 中,會檢查區塊時間以防止同一區塊內的搶先交易。由於呼叫 revealSolution() 需要提交明文答案,這一步驟旨在防止他人繞過 commitSolution() 直接呼叫 revealSolution()。成功驗證後,如果答案被確認正確,獎勵將被分發。
結論
智能合約在區塊鏈技術中扮演著至關重要的角色,並提供了眾多優勢。首先,它們實現了去中心化、自動化執行,無需第三方即可確保交易安全和可靠性。其次,智能合約減少了中間步驟和成本,提高了交易效率。
儘管有如此多的好處,智能合約也面臨著可能給用戶造成經濟損失的攻擊風險。因此,對鏈上用戶來說,一些習慣至關重要。首先,用戶應該始終謹慎選擇互動的去中心化應用程序,並仔細審查合約代碼和相關規則。此外,他們應該定期更新並使用安全的錢包和合約互動工具,以降低黑客攻擊的風險。再者,建議將資金存儲在多個地址中,以最大程度地減少可能因合約攻擊而造成的損失。
對於業界參與者來說,確保智能合約的安全性和穩定性同樣重要。首要任務應該是加強對智能合約的審核,以識別和糾正潛在的漏洞和安全風險。其次,業界參與者應該及時了解與合約攻擊相關的最新區塊鏈發展,並採取相應的安全措施。最後但同樣重要的是,他們還應加強用戶教育和安全意識,特別是在正確使用智能合約方面。
總的來說,通過用戶和業界參與者的共同努力,智能合約帶來的安全風險可以顯著減少。用戶應該始終謹慎選擇合約並保護個人資產,而業界參與者則應加強合約審核、緊跟技術進步,並提高用戶教育和安全意識。我們共同努力,將推動智能合約的安全可靠發展。
參考資料:
Solidity 範例:https://solidity-by-example.org/
慢霧區塊鏈安全知識庫:https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4ODQ3NTM2OA==&action=getalbum&album_id=1378673890158936067&scene=173&from_msgid=2247498135&from_itemidx=1&count=3&nolastread=1#wechat_redirect
Chainlink - DeFi 安全最佳實踐 Top 10:https://blog.chain.link/defi-security-best-practices/#post-title
WTF - Solidity 104 合約安全:https://www.wtf.academy/solidity-104/
DeFi 智能合約漏洞 4 大類 38 種場景:https://www.weiyangx.com/381670.html
OpenZeppelin:https://github.com/OpenZeppelin/