比原链开发笔记 : 如何打造一个安全可持续运行的区块链系统

区块链技术巴比特2017-11-25 06:58:21  阅读 -评论 0

最近BitcoinCash(BCC)的问世,以及以太坊Parity钱包发生15万个以太币被盗事件, 说明无论是漏洞还是版本升级引起的分叉,区块链系统都面临着被攻击的危险。包括之前出现的DAO事件,交易所失窃事件,一系列的问题都可能让区块链投资者备受财务和精神双重损失。

据历史统计,即使是最安全的比特币区块链, 运行过程都大概出现了数十次的BUG, 软件系统产生问题不可避免,但如何让故障概率降到最小,同时在故障生产时如何快速隔离问题,这是每个区块链的设计和开发者需要认真考虑。

本文对历史上出现的重要一些漏洞进行案例分析,归纳出注意事项。比原链类似比特币采用UTXO和POW共识模型,又加入了图灵完备的智能合约系统,研究历史的教训对我们大有裨益, 当然也希望其他链的开发者在设计自己的区块链可以避免出现类似的漏洞,以及可以安全地进行版本升级。

比原链开发笔记 : 如何打造一个安全可持续运行的区块链系统

1、非图灵完备脚本系统的攻击

比特币的交易数据结构是由很多脚本操作码构成的,攻击者设计了多种交易结构类型使用这些操作码对节点进行拒绝服务攻击
比如0.3.5版本之前的比特币系统允许攻击者使用OP_LSHIFT脚本进行拒绝服务攻击。

switch (opcode)        ......    case OP_LSHIFT:      if (bn2 < bnZero)        return false;      bn = bn1 << bn2.getulong();      break;

这是0.3.2版本中script.cpp中描述OP_LSHIFT的代码。OP_LSHIFT是一个数值运算的操作码,功能是bn2.getulong()左移bn1位并保留符号位得到bn。攻击者可能采用位运算让输出溢出的方式使进程崩溃,从而达到拒绝服务攻击的效果。

if (opcode == OP_CAT ||   opcode == OP_SUBSTR ||    opcode == OP_LEFT ||    opcode == OP_RIGHT ||    opcode == OP_INVERT ||   opcode == OP_AND ||    opcode == OP_OR ||    opcode == OP_XOR ||    opcode == OP_2MUL ||    opcode == OP_2DIV ||    opcode == OP_MUL ||   opcode == OP_DIV ||    opcode == OP_MOD ||    opcode == OP_LSHIFT ||    opcode == OP_RSHIFT) return false; // Disabled opcodes.

这是0.3.5以后版本中script.cpp描述OP_LSHIFT的代码。OP_LSHIFT已经直接被禁用,可以看到还包括废除了其他多种数值操作符,这些都可能会导致缓冲区溢出的问题, 在设计虚拟机系统时需要格外注意风险。 当然OP_LSHIFT等脚本操作码只是被禁用,并没有直接从代码里删除,这是为了兼容以前客户端版本数据的合法性, 如果想要重新启用这些脚本,就需要一次硬分叉。

另外,攻击者还会利用操作码的计算复杂性来进行攻击,比如在#71036中区块发现了几个OP_CHECKSIG,这种命令会使节点做很多不必要的操作。
mTemplates.insert(make_pair(TX_PUBKEYHASH, CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG));

这是0.3.2版本中OP_CHECK是加密脚本,整个交易的输入、输出、脚本(从最近执行OP_CODESEPARATOR)都要哈希。所以当一个交易里面有几个OP_CHECKSIG的时候,会使交易进行重复哈希,有可能引起内存消耗过大甚至直接挂起,从而达到拒绝服务攻击的效果。

bool CScriptCompressor::IsToPubKey(std::vector &pubkey) const { if (script.size() == 35 && script[0] == 33 && script[34] == OP_CHECKSIG && (script[1] == 0x02 || script[1] == 0x03)) { pubkey.resize(33); memcpy(&pubkey[0], &script[1], 33); return true; } if (script.size() == 67 && script[0] == 65 && script[66] == OP_CHECKSIG && script[1] == 0x04) { pubkey.resize(65); memcpy(&pubkey[0], &script[1], 65); CKey key; return (key.SetPubKey(CPubKey(pubkey))); // SetPubKey fails if this is not a valid public key, a case that would not be compressible } return false; }

0.8版本对OP_CHECKSIG进行了限制,在这部分代码中首先匹配各脚本命令所占字节和所在位置,如果不正确,就不会继续执行压缩交易。

正确且合理使用OP指令是重要前提, 同时在设计时不要急于为了扩展功能把复杂步骤合并为一个OP指令, 这样可能存在非原子性操作BUG或者易于被DDOS攻击。

2.   参数限制不规范的漏洞

在#74638区块中,攻击者在交易中产生了超过184亿比特币,并发送到网络上的两个地址。几个小时之内,在修复错误后,交易记录被从事务日志中删除,并将网络分配到比特币协议的更新版本。这是在比特币历史上发现和利用的唯一重大安全漏洞。

"out" : [ { "value" : 92233720368.54277039, "scriptPubKey" : "OP_DUP OP_HASH160 0xB7A73EB128D7EA3D388DB12418302A1CBAD5E890 OP_EQUALVERIFY OP_CHECKSIG" }, { "value" : 92233720368.54277039, "scriptPubKey" : "OP_DUP OP_HASH160 0x151275508C66F89DEC2C5F43B6F9CBE0B5C4722C OP_EQUALVERIFY OP_CHECKSIG" } ]

上述输出产生两个90多亿的比特币原理如下: 比特币只检查UTXO的输入是否大于等于输出,如果大于等于交易就成立。

in: 0.50 BTC
out:92233720368.54277039,92233720368.54277039
fee:0.51 92233720368.54277039+92233720368.54277039= -0.01

两个90多亿的19位双精度浮点数相加等于-0.01。所以这笔交易通过了。

此外,0.7.2版本以前,攻击者可以发送一系列的消息包含一个整数整除0在Bloom Filter处理代码,这是对参数使用不规范导致可以远程导致Bitcoin-qt和bitcoind崩溃。数值精度和范围的控制在设计数据结构字段中需要极端慎重, 不仅是安全问题,同时可能尽可能减少磁盘占用空间。

3.   私钥保护缺陷

在0.4.1-0.5.0版本中比特币私钥并没有被加密,dat文件被加密。比特币私钥有可能被盗走。问题在于管理比特币私钥的是BSDDB数据库引擎。当你使用数据库删除某一数据的时候,他只是标记这个数据被删除了,而不是把数据域。新生成的bat文件也只是追加到数据的后面,而不是覆盖。

另外当程序设计账户私钥管理系统时,特别注意在于缓存私钥和随机数种子时, 操作系统内存的管理, 在不需要缓存或者关闭客户端时, 清除在内存中储存的敏感数据。

4. 比特币节点的网络攻击

0.7.0版本,比特币协议有一个预警系统,用来传播关于数字货币的重要新闻。对于收到的每个警报,节点都会检查警报签名。每一项检查都需要一段时间,通常在1到4秒之间。验证时间并不取决于签名的正确性。因此,攻击者可能会在不付出代价的时候向节点注入无效的警报,并耗尽受害者的节点CPU。

如果一个恶意节点发送以每秒3000次警报每秒64Kb的频率向受害节点发送警报,会使受害节点的CPU消耗100%去关联 ThreadMessageHandler2()线程。

即使是发送正确警报的节点,在64字节/秒的连接上连续发送一个188字节的正确警告仍然会使受害者的CPU使用率上升到100%。

解决办法:

断开任何发送坏警报节点的网络连接 检查一个警告是否有超过一次的验证,超过一次的警告就直接拒绝 void ThreadMessageHandler2(void* parg) { printf("ThreadMessageHandler started\n"); #ifdef __WXMSW__ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); #else setpriority(PRIO_PROCESS, getpid(), PRIO_MIN); #endif loop { // Poll the connected nodes for messages vector vNodesCopy; CRITICAL_BLOCK(cs_vNodes) vNodesCopy = vNodes; foreach(CNode* pnode, vNodesCopy) { pnode->AddRef(); // Receive messages TRY_CRITICAL_BLOCK(pnode->cs_vRecv) ProcessMessages(pnode); // Send messages TRY_CRITICAL_BLOCK(pnode->cs_vSend) SendMessages(pnode); pnode->Release(); } // Wait and allow messages to bunch up vnThreadsRunning[2]--; Sleep(100); vnThreadsRunning[2]++; if (CheckForShutdown(2)) return; } }

可以看到检查时间并不取决于警报的正确性,只要调用ThreadMessageHandler2线程就会sleep(100)。

5.   图灵完备合约系统更复杂,漏洞几率更大

以以太坊为例,以太坊区块链账户模型是account模型,因为具有图灵完备的智能合约系统,实现起来比比特币更复杂,使得在以太坊上出现的很多智能合约都存在漏洞。The DAO 和最近出现的Parity钱包漏洞就是很好的例子。下面主要分析一下Parity钱包漏洞。

Parity Multisig电子钱包版本1.5+的漏洞被发现,使得攻击者从三个高安全的多重签名合约中窃取到超过15万ETH(约3000万美元)。攻击原理如下:

成为合约的owner

function() payable{ if(msg.value > 0) Deposit(msg.sender,msg.value); else if(msg.data.length > 0) _walletLibrary.delegatecall(msg.data); }

通过往这个合约地址转账一个value = 0 ,msg.data.length > 0 的交易, 执行到_walletLibrary.delegatecall的分支,该函数能无条件的调用合约内的任何一个函数,黑客调用了一个叫做 initWallet的函数:

function initWallet(address [] _owners,uint _required,uint _daylimit){ initDayLimit(_daylimit); initMultiowned(_owners,_required); }

这个函数再次调用initMultiowned函数:

function initMultiowned(address [] _owners,uint _required){ m_numOwners = _owners.length + 1; m_owners[1] = uint(msg.sender) m_ownerIndex[uint(msg.sender)] = 1; for(uint i=0;i<_owners.length;++i) { m_owners[2+i]=uint(_owners[i]); m_ownerIndex[uint(_owners[i])] = 2+i; } m_required = _required; }

但是,initWallet没有检查以防止攻击者在合同初始化后调用到initMultiowned, 这个函数使得这个合约的所有者被改为攻击者。

在parity钱包源代码里修改漏洞的时候仅仅改了一个词,把权限问题划分清楚。

- function initDaylimit(uint _limit) internal + function initDaylimit(uint _limit) only_uninitialized

由此我们可以看到使用图灵完备的智能合约系统时一定要注意合约之间、合约的函数之间相互调用时产生的权限和逻辑问题。

当然建议在设计图灵完备合约系统时,摈弃大而全的设计,当性能和功能会相互挚肘时, 还是应该选择最小可用性和性能优先的原则。系统设计最难做到的部分是放弃,如果区块链是金融系统的一部分,那么稳定安全是第一要务,复杂的功能只能增加系统性风险。

6. 协议版本升级的风险

0.3.13版本,比特币进行了版本升级,每笔交易都要有0.01的比特币的交易费,导致了很多微小交易没人打包变成了无效的交易,这些无效的交易放在钱包里可能被继续交易而一直没有被确认。比特币开发者又更改了版本,小额交易(t<0.01)不需要支付交易费。

此外,版本升级还会引起重复验证交易,可能会存在一种处理重复交易的攻击方式。 存在重复验证交易可能会有两种形式,一是故意添加进来的重复交易。二是版本升级时旧区块与新区块产生重复交易。比如一条交易信息广播出来,新旧两个版本的客户端分别使用新旧两个版本号打包交易,当验证交易的时候,同一个交易的UTXO就会被查找两次,很有可能造成双重支付攻击。BIP30已经解决,在同一条链中,不允许已经验证的交易的标识符与以前的、未完全花费的标识符匹配。只有当前交易的前一个交易没有可输出的输出时,重复交易才可被使用。

总结与相关建议

从上面的分析我们可以看到,区块链也避免不了漏洞、攻击。当出现漏洞需要修补漏洞和提升性能的时候,就需要进行软件升级,说到版本升级就必须说一下软分叉和硬分叉的区别 。

区块链升级伴随着新的版本号的出现,区块数据格式和交易的数据格式都会更换新的版本号,升级软件的矿工可以不接受旧版本号的交易消息,没有升级软件的矿工接收新版本号的交易消息可以称为软分叉,软分叉最终会达成新版本号一致,硬分叉就是一刀切,升级软件的矿工不接受旧的版本号的消息,没有升级软件的矿工不接受新的版本号的交易消息。

软硬分叉可以用这两个公式来表示:

软分叉:a = 1; a = 1+2; a = 2; 硬分叉:a =1 ; a = 2;

版本升级时需要考虑的问题:

考虑大部分网络支持规则之前/之后运行的旧/新软件的所有四种组合 考虑与老客户/矿工的向后兼容 在主网部署之前先在测试网络上可用 逐步推出变革,一步一步来

升级流程建议

不接受非标准的交易到内存池,如果它有一个未知的版本号 使用代码对过去1000个区块的版本号进行计数 如果最近的区块有55%或更多的版本号有未知版本号,则警告用户需要升级

升级的方案 说明如何以最小化风险和中断的方式处理未来升级的可能性

必须设计操作码以便有任何一个交易不通过时都可视为无操作。当攻击者根据新规则进行有效的新交易攻击时,对旧的矿工/客户端无效,导致块分裂 使用新操作码的交易将被给予新版本号 运行新代码的矿工产生具有新版本号的块,因此可以测量对新功能的支持 较旧的节点不会中继或挖掘新的交易,也不会占用新交易的交易

比特币bug总结

Parity多重签名钱包被盗事件之技术分析

Blockchain Rule Update Process

声明:链世界登载此文仅出于分享区块链知识,并不意味着赞同其观点或证实其描述。文章内容仅供参考,不构成投资建议。投资者据此操作,风险自担。此文如侵犯到您的合法权益,请联系我们100@7234.cn

    参与讨论 (0 人参与讨论)

    相关推荐

    区块链投资趋势报告:巨头入场布局行业趋于成熟

    区块链投资趋势报告:巨头入场布局行业趋于成熟

    来自:https://mp.weixin.qq.com/s?__biz=MzI4NzIxOTY1NA==&amp;mid=2650632639&amp;idx=1&amp;sn=e6d1c29731d992a80410aaee82ec3ea6&amp;chksm=f3d8db16c4af520097e4a64a71b1d4743ac326b9f027

    重新发明货币

    重新发明货币

    一、货币的演化过程 先简单回顾一下人类货币的演化过程,大概有以下阶段: a. 1.0版本:自然货币(贝壳、牲口、金银……) 这个阶段,货币基于一般等价物的稀有性或者实用性,货币不可能出现人为操纵的超发。 b. 2.0版本:早期纸币、银票到本位纸币 当贸易量越来越大,实物货币太不方便了,而且大家发现其实并不在意货币本身有什么价值,在意的只是这么多的货币能不能交换到足够的物品,于是纸币这种信用货

    从比特币交易看欧洲央行虚拟货币分类

    从比特币交易看欧洲央行虚拟货币分类

      互联网对传统社会的颠覆从未停止,在其完成对信息流、商流、物流、资金流的初步改造之后,或将以虚拟货币的形式打破现有货币体系   4月18日,在中国极客张沈鹏创办的比特币交易平台(42BTC.com)上,比特币对人民币的平均交易价为576元。当天,该平台完成了100个比特币的交易量。仅仅过去一周,4月25日上午,比特币对人民币的平均交易价已达到906元。据42BTC网站统计:在过去的32个月

    欧洲央行-比特币报告

    3.1 比特币 3.1.1 基本特征          比特币可能是最成功的,也可能是最有争议的虚拟货币方案,由日本程序员中本聪(译者注:事实上,中本聪是不是日本人,甚至是不是单个人无从考证)在2009年设计并实现。该计划基于一个类似于BitTorrent的P2P网络。BitTorrent是互联网上著名的共享文件协议,应用在电影,游戏和音乐领域。比特币在全球层面上运作,可用于各类货币交易(虚

    彻底玩转比特币地址和私匙

    彻底玩转比特币地址和私匙

    比特币地址和私匙是所有比特币初学者面对的一大难题,再加上那一串超长的字符串,让人更是摸不到头脑。 现在编者以问答的形式,带你一步步的揭开比特币地址和私匙的面纱。 还不知道什么是比特币地址和私匙的同学请点这里 问题一、比特币钱包由什么组成? 答 我们知道,比特币地址和私匙组成了比特币钱包,而私匙则决定了比特币地址上比特币的归属。 地址和私匙 问题二、如果只记得私匙我们还能还原比特币地址么? 答

    用GO语言实现比特币算法

    用GO语言实现比特币算法

    本节的这个例子展示一点点高精度数学包math/big、一点点散列包hash、一点点加密包crypto,还有一点点测试包testing的知识。这里不介绍bitcoin协议和算法——尽管它们很有趣,而是试图指出,Go对多种操作系统的支持,是实现这种跨平台应用的理想语言。 位钱(bitcoin)是一种使用加密手段制作的分布式电子货币。它最初于1998年由Wei Dai提出,并由中本聪(Satoshi

    详解比特币的找零机制

    详解比特币的找零机制

    比特币的找零机制一直让人有些迷惑,明明只向一个地址发送了比特币为什么 blockchain 上面的显示的有时是1个地址对多个地址,有时是多个地址对1个地址,有时又显示多个地址对多个地址? 为什么比特币资深用户要提醒大家当比特币钱包交易100次以上时再次交易后要重新备份钱包,恢复以前的钱包备份有可能会遭遇损失? 是的,这一切都是因为比特币的找零(Change)机制。本文参考 Bitcoin的维

    玩转比特币客户端之一:C盘转移和加速下载

    玩转比特币客户端之一:C盘转移和加速下载

    C盘空间不足?交易数据下载速度太慢?别着急,乐享比特币教你轻松玩转比特币官方客户端。 所有新人开始接触比特币时做的第一件事情大多数是安装比特币的官方客户端。 安全起见大家最好直接访问官方发布渠道sourceforge的地址进行下载:http://sourceforge.net/projects/bitcoin/files/Bitcoin/ 该网页列出了各版本的官方比特币客户端,目前

    麦妖榜
    更新日期 2019-06-16
    排名用户贡献值
    1BitettFan23992
    2等待的宿命23809
    3六叶树20309
    4区块大康18606
    5天下无双16192
    6linjm122715948
    7牛市来了15758
    8lizhen00215077
    9让时间淡忘14475
    10冷风大q11188
    返回顶部 ↑