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),這類指令由於需要進行橢圓曲線運算,執行開銷遠高於其他指令。因此在節點代碼中,對於這類指令的數量做出了限制,以避免拒絕服務攻擊。

相關代碼位置:

https://github.com/Bitcoin-ABC/bitcoin-abc/blob/f27da0752c0a3b7382df54a65ca3cf1c3629aad4/hide/consensus/consensus.h

static const uint64_t MAX_TX_SIGOPS_COUNT = 20000;

即,單個 Transaction 中 SigOP 的數量不能超過 20000。

漏洞原理

細心的話,你已經發現了,攻擊載荷的 SigOP 數量爲 1334*15 = 20010,這個攻擊載荷 TX 會被節點拒絕,報錯即是 too many sigops,這是導致節點拒絕包含該 TX 的區塊的原因。

相關代碼位置:

https://github.com/Bitcoin-ABC/bitcoin-abc/blob/f27da0752c0a3b7382df54a65ca3cf1c3629aad4/hide/validation.cpp

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

相關代碼位置

https://github.com/Bitcoin-ABC/bitcoin-abc/blob/f27da0752c0a3b7382df54a65ca3cf1c3629aad4/hide/validation.cpp

// 原代碼 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 中我們可以找到他們。

相關代碼位置:

https://github.com/Bitcoin-ABC/bitcoin-abc/blob/f27da0752c0a3b7382df54a65ca3cf1c3629aad4/hide/policy/policy.h

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 漏洞。