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),这类指令由于需要进行椭圆曲线运算,执行开销远高于其他指令。因此在节点代码中,对于这类指令的数量做出了限制,以避免拒绝服务攻击。

相关代码位置:/src/consensus/consensus.h#L27

static const uint64_t MAX_TX_SIGOPS_COUNT = 20000;

即,单个Transaction中SigOP的数量不能超过20000。

漏洞原理

细心的话,你已经发现了,攻击载荷的SigOP数量为 1334 * 15 = 20010,这个攻击载荷TX会被节点拒绝,报错即是too many sigops,这是导致节点拒绝包含该TX的区块的原因。

相关代码位置:/src/validation.cpp#L1936

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

相关代码位置:/src/validation.cpp#L592

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

相关代码位置:/src/policy/policy.h#L108

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

发表评论

电子邮件地址不会被公开。