5 月 15 日 BCH 升級遭到攻擊,慢霧安全團隊及時跟進,並在社區裏注意到相關分析工作,通過交流將此分析文完整轉載於此。這是一場真實攻擊,從行爲上分析來看確實預謀已久,但 BCH 響應很及時,成功化解了一場安全危機。
原文標題:《BCH 升級攻擊事件安全分析》
文章來源:公衆號 慢霧科技
作者:劉一鳴
BCH 的 5 月 15 日 升級遭到攻擊,導致節點報出 too many sigops 錯誤。經分析,攻擊載荷爲一個精確構造的 P2SH Transaction,利用了 BCH 去年 11 月升級引入的 OP_CHECKDATASIG 操作碼。
攻擊導致了礦工節點無法打包,BCH 方面通過類似於空塊攻擊的方式,緊急挖出十個空塊以觸發滾動檢查點保證升級。攻擊發生約 1 小時後,BCH 礦池上線緊急修復後的代碼成功繼續出塊。
不過同時也有人觀察到,在 582698 區塊高度,有礦工挖出了哈希結尾爲 6bf418af 的區塊,大小 139369 字節。但隨後該區塊被 10 分鐘後 BTC.top 挖出的哈希結尾爲 449e2bb4 區塊所重組。或許是一次誤傷,不過可見 BCH 對於升級防守之嚴密。
攻擊原理分析
攻擊載荷
捕捉到的攻擊載荷 TXID 爲
4c83ab55623633c86ec711b3d68ccdea506b228178ff1533f287ab744b006c44
該攻擊載荷由 1334 個 Input 構成,每一個 Input 均是 P2SH 格式。
其內容爲
OP_FALSE OP_IF OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG
OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG
OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG
OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG OP_14 OP_CHECKDATASIG OP_ENDIF
OP_TRUE
可見其中包含 15 個 OP_CHECKDATASIG
該攻擊載荷利用了一個 CORE 曾經幫 ABC 修復,但未完全修復的漏洞,製造了組塊和驗證之間的差異,從而導致礦工組出的區塊不被節點接受。
漏洞背景
OP_CHECKDATASIG 是一種橢圓曲線簽名校驗指令 (SigOP),這類指令由於需要進行橢圓曲線運算,執行開銷遠高於其他指令。因此在節點代碼中,對於這類指令的數量做出了限制,以避免拒絕服務攻擊。
相關代碼位置:
static const uint64_t MAX_TX_SIGOPS_COUNT = 20000;
即,單個 Transaction 中 SigOP 的數量不能超過 20000。
漏洞原理
細心的話,你已經發現了,攻擊載荷的 SigOP 數量爲 1334*15 = 20010,這個攻擊載荷 TX 會被節點拒絕,報錯即是 too many sigops,這是導致節點拒絕包含該 TX 的區塊的原因。
相關代碼位置:
if (nSigOpsCount > nMaxSigOpsCount) { return state.DoS(100, error(\"ConnectBlock(): too many sigops\"), REJECT_INVALID, \"bad-blk-sigops\");}
然而在組塊時爲什麼沒有拒絕這個 TX 呢?我們可以在 Bitcoin-ABC 的補丁中發現端倪。
補丁位置:https://reviews.bitcoinabc.org/D3053
相關代碼位置
// 原代碼 int64_t nSigOpsCount = GetTransactionSigOpCount(tx, view, STANDARD_SCRIPT_VERIFY_FLAGS); // 補丁代碼 int64_t nSigOpsCount = GetTransactionSigOpCount(tx, view, STANDARD_CHECKDATASIG_VERIFY_FLAGS);
所在的函數爲:AcceptToMemoryPoolWorker
可見原代碼組塊過程中在計算 Transaction 中的 SigOP 數量時,錯誤地使用了 STANDARD_SCRIPT_VERIFY_FLAGS,而非 STANDARD_CHECKDATASIG_VERIFY_FLAGS。
這兩個標誌有什麼區別呢?
在 policy 中我們可以找到他們。
相關代碼位置:
static const uint32_t STANDARD_CHECKDATASIG_VERIFY_FLAGS = STANDARD_SCRIPT_VERIFY_FLAGS | SCRIPT_ENABLE_CHECKDATASIG;
所以我們可以見到,當僅使用了 STANDARD_SCRIPT_VERIFY_FLAGS 時,計算腳本中 SigOP 數量時,是不包含 OP_CHECKDATASIG 的。所以這個包含 20010 個 SigOP 的攻擊載荷,在組塊時,統計出來的 SigOP 數量爲零。
因此,攻擊載荷會在礦工組塊的時候被包含進區塊中,然而,由於其他代碼正確地統計了 SigOP,節點會拒絕該區塊,這導致了 BCH 無法出塊。
總結
攻擊者利用了 BCH 引入 OP_CHECKDATASIG 時產生的,又未完全修復的漏洞,巧妙地構造了攻擊載荷。攻擊者應該高度瞭解客戶端代碼,並熟悉 OP_CHECKDATASIG 漏洞。