iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
Security

從 Detect 到 Proof:智慧合約審計工具實戰系列 第 10

Day 10|找到問題之後,如何記錄?

  • 分享至 

  • xImage
  •  

這個系列已經寫到第十天,先整理一下前面討論過的東西。

Day 1 到 Day 3 是準備:工具沒有報錯,合約是不是就安全?漏洞有哪些分類方式?Slither、Foundry、Certora 各自交出的證據又差在哪裡?

Day 4 到 Day 6 開始跑 Slither。先在 FlippazOne 上找到沒有權限檢查的提款函式,再拆 Slither 怎麼把原始碼變成 AST、CFG、SlithIR,最後看它的 Impact 和 Confidence 代表什麼。

Day 7 到 Day 9 看 Slither 抓不到、或抓得到的例子。Level Finance 的重複領取逃過了 Slither,Grim Finance 的 Reentrancy 則抓得到。最後自己寫了一個 detector,標出批次輸入的風險。

接下來讓我們討論要怎麼將問題記錄下來

先訂優先程度

Day 6 提過,Slither 的 Impact 和 Confidence 描述的是這一類 Finding 的影響和分析信心。所以每一筆 arbitrary-send-eth 都會標 High,就算它在你的合約裡一毛錢都送不出去。實際損失多少、函式能不能被呼叫、合約裡有沒有資產,還是要另外確認。

Finding 出來之後,還要自己再判斷一次。

三軸分級

那要怎麼判斷?我參考 Sherlock、Code4rena、Cantina 三個審計平台的判定標準,把每筆 Finding 拿三個問題來問:

判斷依據
可利用性 誰能觸發?前置條件做得出來嗎? 任何人都能呼叫,不需要特殊狀態 只有 owner 能呼叫,或前置條件做不出來
資產影響 觸發之後,誰的什麼東西受損? 資金被轉走、鎖住,或合約停擺 只是少了一個 event 或寫法不好看
證據強度 目前手上的證據能證明到哪裡? 有 PoC 或事故交易重現 只有 detector 的一行描述

Sherlock 要求寫出完整的攻擊路徑和實際損失,Code4rena 依資金損失、功能不能用、griefing 分級,Cantina 則是用影響乘上可能性的矩陣。

三軸看完之後,就能訂出優先程度。可利用性和資產影響都高,例如任何人都能直接轉走資金,是 High,要最先處理。只在特定條件下才會出事,或是會讓功能不能用但錢不會被拿走,是 Medium。可利用性或資產影響其中一個是低,例如只有 owner 能呼叫,或根本沒有資產受影響,就是 Low 或 Informational,排在最後。證據強度不影響等級,但會決定這個判斷站不站得住,證據不夠的要先補 PoC。

範例

FlippazOne 有 40 筆 Finding,下面挑兩筆 Slither 都標 High 的來看,兩筆最後的優先程度差很多。

ownerWithdrawAllTo:High

Slither 的判斷是 arbitrary-send-eth,Impact High、Confidence Medium。

function ownerWithdrawAllTo(address toAddress) public  {
    (bool success, ) = toAddress.call{value: address(this).balance}("");
    require(success, "Failed to withdraw funds.");
}

先看可利用性。函式是 public,沒有 onlyOwner,也沒有任何 require 檢查呼叫者是誰,連拍賣有沒有結束都不檢查。所以任何人、任何時間都能呼叫,也不需要先做什麼準備。可利用性是高。

再看資產影響。轉出的金額是 address(this).balance,也就是合約裡全部的 ETH,裡面包含還沒退回去的出價。收款地址 toAddress 是呼叫者自己傳進來的,所以錢會直接進攻擊者的地址。資產影響也是高。

最後是證據強度。這筆不用自己寫 PoC,Day 4 看過的事故交易就是證據:2022 年 7 月 6 日,有人呼叫這個函式,傳入自己的地址,把合約裡的 ETH 全部轉走了。證據強度是高。

三軸都是高,訂為 High。

projectProxy:Informational

Slither 的判斷是 uninitialized-state,Impact High、Confidence High,Confidence 比上一筆還高。

Slither 說 projectProxy 從來沒有被初始化過。它是一個 mapping:

mapping(address => bool) public projectProxy;

我在整份合約裡搜尋 projectProxy,只出現兩次,一次是上面的宣告,另一次在 isApprovedForAll

function isApprovedForAll(address _owner, address operator) public view override(ERC721) returns (bool) {
    OpenSeaProxyRegistry proxyRegistry = OpenSeaProxyRegistry(proxyRegistryAddress);
    if (address(proxyRegistry.proxies(_owner)) == operator || projectProxy[operator]) return true;
    return super.isApprovedForAll(_owner, operator);
}

沒有任何函式會寫入 projectProxy,所以每個地址查出來都是預設值 falseprojectProxy[operator] 永遠不成立,這個條件等於不存在。

先看可利用性。攻擊者沒辦法讓 projectProxy 變成 true,因為根本沒有寫入的地方。可利用性是低。

再看資產影響。這個條件如果會成立,某個地址就能操作別人的 NFT,那才危險。但它永遠是 false,結果只是少了一條授權的路,不會多出一條。沒有人會因此多拿到權限,也沒有資產受影響。資產影響是低。

證據強度是高,因為只要讀程式碼就能確認,這兩行就是全部用到它的地方。

可利用性和資產影響都是低,訂為 Informational,不算漏洞。它比較像開發者原本想做「專案方代理授權」,最後忘了寫設定函式。

兩筆的 Impact 都是 High,projectProxy 的 Confidence 還更高,但最後一筆是 High,一筆是 Informational。

寫成審計報告

訂好優先程度之後,要寫成報告交出去。各平台的格式不太一樣,下面參考 Sherlock 報告的欄位,把 ownerWithdrawAllTo 寫一次。Sherlock 會把等級放在標題前面,High 的第一筆寫成 H-1。

H-1:任何人都能轉走合約裡全部的 ETH

摘要

FlippazOne.ownerWithdrawAllTo 沒有權限檢查,任何人都能把合約裡的 ETH 全部轉到自己的地址。

原因

FlippazOne.sol:1359-1362ownerWithdrawAllTopublic,沒有 onlyOwner,函式裡也沒有檢查 msg.sender。收款地址 toAddress 由呼叫者傳入,轉出金額是 address(this).balance

前置條件

合約裡有 ETH,例如拍賣期間累積的出價。

攻擊路徑

  1. 攻擊者呼叫 ownerWithdrawAllTo(攻擊者地址)
  2. 合約把 address(this).balance 全部轉給攻擊者。

影響

合約裡全部的 ETH 被轉走,包括得標者付的錢,以及還沒退回給其他出價者的錢。

證據

2022 年 7 月 6 日的事故交易就是這條路徑,見 Day 4。

修正建議

加上 onlyOwner。同一份合約的 ownerWithdrawToownerWithdrawownerWithdrawAll 也都要一起加。

跟前面三軸對照,攻擊路徑和前置條件在回答可利用性,影響在回答資產影響,證據在回答證據強度。前面訂優先程度時問過的問題,寫報告時要一個一個寫出來。

不過 ownerWithdrawAllTo 的證據是現成的事故交易,大部分的漏洞沒有現成的交易可以看。下一篇回到 Level Finance,把它的重複領取縮成一份可以測試的合約,開始自己準備證據。


上一篇
Day 9|動手寫一個 Slither Detector
系列文
從 Detect 到 Proof:智慧合約審計工具實戰10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言