内容简介:本期DVP漏洞专题是比特币漏洞CVE-2010-5141,这个漏洞可以导致攻击者盗取任何人的比特币,危害十分严重。万幸的是该漏洞并没有被利用过,而且修复速度极快。该漏洞与比特币的脚本引擎有关,对公链开发者具有参考意义;从当下市场上的公链来看,大多数都内置了虚拟机或脚本引擎,以此来打造DApp生态,同时也是区块链的大趋势之一。Tips:在漏洞代码片段中会涉及一些UTXO的相关知识、概念,所以对该漏洞进行理论分析之前需要先了解一下这些知识点,已经了解的可以直接跳过。
本期DVP漏洞专题是比特币漏洞CVE-2010-5141,这个漏洞可以导致攻击者盗取任何人的比特币,危害十分严重。万幸的是该漏洞并没有被利用过,而且修复速度极快。该漏洞与比特币的脚本引擎有关,对公链开发者具有参考意义;从当下市场上的公链来看,大多数都内置了虚拟机或脚本引擎,以此来打造DApp生态,同时也是区块链的大趋势之一。
一、比特币当中的UTXO模型是什么?
Tips:在漏洞代码片段中会涉及一些UTXO的相关知识、概念,所以对该漏洞进行理论分析之前需要先了解一下这些知识点,已经了解的可以直接跳过。
Ⅰ、账户模型与UTXO模型
我们在看 UTXO模型
之前先说说常见的 账户模型
,什么是 账户模型
? 账户模型
的数据结构简单可以理解为“账号=>余额”,每个账号都对应一个余额。举个例子:若账号A向账号B转账200,在账户模型中完成这个转账操作只需要A-200然后B+200;目前大部分软件都采用的是账户模型,比如银行系统、以太坊等等。
而比特币却使用了自行研发的UTXO模型,UTXO中是没有“账号=>余额”这样的数据结构的,那怎么进行转账?
Ⅱ、比特币如何操作转账
以上面A向B转账为例,在UTXO中完成这个转账需要以下操作:
- 找到A账号下200余额的来源,也就是意味着要找到A收款200的这笔交易x
- 以x交易为输入,以向B转账200的交易y为输出,x与y对应且x与y的转账金额必须相等
- x交易被标记为已花费,y交易被标记为未花费
两笔交易的转账金额必须相等,简单解释就是收到多少就只能转出多少,实际上确实是这样。
但是又必须只给别人转一部分的时候怎么办?答案是只向他人转一部分,然后剩下的一部分转给自己另外一个号。
Ⅲ、引用两张来自网络的图文:
在本文当中比特币为什么采用UTXO模型不是重点,我们了解UTXO的原理即可。
⠀
二、比特币的脚本引擎
比特币脚本是非图灵完备的。比特币使用自行定义的一种脚本进行交易和其他的操作,为比特币提供有限的灵活性。实现诸如多重签名、冻结资金等简单功能,但更多的就不行了。
比特币这么做的原因是牺牲一定的完备性来保障安全性。比特币脚本的原理是先定义了一堆操作码,然后脚本引擎基于堆栈来逐个执行每个操作码。
堆栈很好理解,队列是先进后出,而堆栈正好相反,是先进先出,将一个元素压入(push)堆栈后该元素会被最先弹出(pop)。
在比特币早期的版本中发送一笔标准转账( pay-to-pubkey
)交易需要脚本签名( scriptSig
)和公钥验证脚本( scriptPubKey
),具体处理流程如下:
先填入要执行的脚本( Script
),然后签名( sig
)和公钥( pubKey
)被压入堆栈,然后操作码 OP_CHECKSIG
会去验证签名等,若验证通过就将true压入堆栈,否则就将false压入堆栈。
三、CVE-2010-5141漏洞分析
了解以上知识后就可以开始分析 CVE-2010-5141
漏洞了。笔者下载了存在漏洞的版本 0.3.3
,下载地址在github的bitcoin仓库中找release.
script.cpp
代码片段 VerifySignature
函数:
执行每个交易都会调用 VerifySignature
函数,该函数用于执行脚本以及验证签名,然后给交易标注是否被花费。
首先 txFrom
参数是上一笔交易, txTo
是正在处理的这笔交易,如果理解了上面的章节中讲解过的 UTXO模型
,这里就不难理解了。重点看 1125行
代码,调用了 EvalScript
函数,第一个参数是 txin.scriptSig
(包含签名信息)+分隔操作码 OP_CODESEPARATOR
+ txout.scriptPunKey
(包含公钥信息、 OP_CHECKSIG
指令),这些就是 EvalScript
函数要执行的脚本,后面的参数可以暂时不用管,只要 EvalScript
函数返回true那么这笔验证签名就通过了。 EvalScript
函数如何才能返回true?
首先堆栈不能是空的,然后栈顶强转bool后必须是true。笔者简单解读为必须要有栈顶而且值不能是0。
然后再看关键的 OP_CHECKSIG
操作码
(注:由于操作码太多,本文针对 OP_CHECKSIG
操作码)
上面代码不难理解,调用
Checksig
函数来验证签名,然后返回给FSuccess变量,如果为真就压一个vchTrue(非0)进栈,否则就压一个vchFalse(0)进栈;如果opcode是 OP_CHECKSIGVERIFY
而不是 OP_CHECKSIG
的话就让vchTrue出栈,并开始执行后面的操作码。
按照 OP_CHECKSIG
的正常逻辑,验证签名不成功的话一定会有一个vchFalse留在栈顶,虽然堆栈不为空,但是栈顶的值是0,还是会返回false。
回到之前的代码, EvalScript
函数执行的脚本主要由以下变量组成:
- txin.scriptSig
- OP_CODESEPARATOR
- txout.scriptPubKey
⠀
第一个签名信息可控,第二个不用管只是一个分割符,会被删掉,第三个不可控,因为是来自上一个交易。
第一个变量可控,而且是作为脚本执行,所以这个变量可以不仅仅是签名信息,还能是opcode,这就好办了,下面需要引用一个神奇的操作码 OP_PUSHDATA4
,我们看看比特币 0.3.3
是怎么处理这个操作码的:
首先获取操作码。如果操作码的值小于或者等 于OP_PUSHDATA4
的值就把vchPushValue全压入堆栈,再跟进 GetOp
函数
经翻阅源码,发现 OP_PUSHDATA4
指令被定义为 78
,所以当函数遇到 OP_PUSHDATA4
时,指针会向又移 78+4=82
位,其中 78
位数据会被压入栈,所以只要在 txin.scriptSig
中注入一个 OP_PUSHDATA4
操作码,后面的公钥信息以及 OP_CHECKSIG
指令都会被”吃掉”并作为参数入栈,当指针走到最后时,进行最后的判断:
- 堆栈是否为空?不是
- 栈顶元素是否为0?不是
于是满足条件 EvalScript
函数返回true,然后 VerifySignature
函数也返回true,既然签名验证都绕过了,那别人的比特币便可以任意花费了。
四、CVE-2010-5141漏洞修复方案
笔者下载了比特币版本0.3.8,直接看关键部分代码
修复方案也很明确,把 scriptSig
和 scriptPubkey
分开执行,无论你 scriptSig
里面有什么也不会影响到后面的 scriptPubkey
执行。
⠀
写在最后:
因为该比特币漏洞来自2010年,素材可以说是相当古老,目前漏洞分析主要存在以下难点:
- 漏洞相关资料非常少,大多数漏洞都只有一个CVE编号和简介,不查阅大量资料无从入手。
- 环境搭建难,难在编译、私链搭建(早期的比特币甚至没有私链这个概念)等,比特币早期的源码编译需要的依赖现在很多都已经不维护并下线了。
基于这些原因,所以笔者仅做了理论研究,并未进行实践验证,如有错误之处还请指正。
五、参考链接
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 类型混淆漏洞实例浅析
- 挖洞姿势:浅析命令注入漏洞
- Java XXE 漏洞典型场景浅析
- Microsoft SQL Server漏洞浅析
- Microsoft SQL Server漏洞浅析
- PDF漏洞(CVE-2018-12794)浅析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Big Java Late Objects
Horstmann, Cay S. / 2012-2 / 896.00元
The introductory programming course is difficult. Many students fail to succeed or have trouble in the course because they don't understand the material and do not practice programming sufficiently. ......一起来看看 《Big Java Late Objects》 这本书的介绍吧!