公链安全之比特币任意盗币漏洞浅析(CVE-2010-5141)
字数 1393 2025-08-29 08:31:41
比特币任意盗币漏洞分析(CVE-2010-5141)教学文档
一、背景知识
1. UTXO模型
1.1 账户模型与UTXO模型的区别
- 账户模型:数据结构为"账号=>余额",转账操作简单(A-200,B+200)
- 代表系统:银行系统、以太坊等
- UTXO模型:比特币特有模型,无直接"账号=>余额"结构
- 代表系统:比特币
1.2 UTXO转账机制
以A向B转账200为例:
- 找到A账号下200余额的来源交易x
- 创建新交易y:
- 输入:交易x
- 输出:向B转账200的交易y
- 标记状态:
- x交易标记为已花费
- y交易标记为未花费
- 金额规则:
- 输入输出金额必须相等
- 如需部分转账,剩余金额需转回自己其他地址
2. 比特币脚本引擎
- 特性:非图灵完备,提供有限灵活性
- 功能:实现多重签名、资金冻结等简单功能
- 执行原理:
- 基于堆栈执行(先进先出)
- 定义了一系列操作码(OP_CODE)
- 标准转账流程:
- 填入执行脚本(Script)
- 签名(sig)和公钥(pubKey)压入堆栈
- OP_CHECKSIG验证签名
- 验证通过压入true,否则压入false
二、漏洞分析(CVE-2010-5141)
1. 漏洞核心
- 位置:比特币0.3.3版本的
script.cpp文件 - 关键函数:
VerifySignature - 影响:攻击者可盗取任意比特币
- 实际影响:未被利用,修复迅速
2. 漏洞代码分析
2.1 VerifySignature函数
// 伪代码表示
bool VerifySignature(CTx txFrom, CTx txTo, ...) {
// 关键调用
if (!EvalScript(txin.scriptSig + OP_CODESEPARATOR + txout.scriptPubKey, ...)) {
return false;
}
return true;
}
txFrom:上一笔交易txTo:当前处理交易EvalScript参数组成:txin.scriptSig(可控,含签名信息)OP_CODESEPARATOR(分隔符)txout.scriptPubKey(不可控,含公钥和OP_CHECKSIG)
2.2 EvalScript函数
- 返回true条件:
- 堆栈不为空
- 栈顶元素强转为bool后为true(非0)
2.3 OP_CHECKSIG操作码
// 伪代码表示
case OP_CHECKSIG:
bool fSuccess = CheckSig(...);
if (fSuccess)
stack.push(vchTrue); // 非0值
else
stack.push(vchFalse); // 0值
2.4 漏洞利用关键 - OP_PUSHDATA4
- 定义值:78
- 处理逻辑:
- 遇到OP_PUSHDATA4时,指针右移78+4=82位
- 其中78位数据被压入栈
- 攻击方式:
- 在
txin.scriptSig中注入OP_PUSHDATA4操作码 - 导致后续的公钥信息和OP_CHECKSIG指令被"吃掉"作为参数入栈
- 最终判断:
- 堆栈不为空
- 栈顶元素非0
- 绕过验证,返回true
- 在
三、漏洞修复方案
修复版本
比特币0.3.8
修复方法
// 修复后伪代码
bool VerifySignature(...) {
// 将scriptSig和scriptPubkey分开执行
if (!EvalScript(txin.scriptSig, ...) ||
!EvalScript(txout.scriptPubKey, ...)) {
return false;
}
return true;
}
- 核心改进:分离执行
scriptSig和scriptPubkey - 效果:
scriptSig中的恶意操作码无法影响scriptPubkey的执行
四、教学总结
1. 漏洞要点
- 根本原因:脚本拼接执行导致注入攻击
- 关键操作码:OP_PUSHDATA4的异常处理
- 利用条件:控制输入脚本(txin.scriptSig)
2. 对公链开发的启示
- 脚本/虚拟机安全:需严格隔离用户输入与系统脚本
- 操作码设计:需考虑边界情况和异常处理
- 验证机制:关键验证步骤应有独立完整的执行环境
3. 研究难点
- 历史资料稀少
- 早期版本编译环境搭建困难
- 私链测试环境缺乏
五、参考资源
- 比特币StackExchange关于该漏洞的讨论
- 比特币0.3.3和0.3.8源码对比