6月28日,慢雾科技发布了一条针对 USDT 的预警和漏洞分析,提醒各大交易所尽快暂停 USDT 充值功能,并自查代码是否存在该逻辑缺陷。全文如下:
#预警# #漏洞分析# 交易所在进行 USDT 充值交易确认是否成功时存在逻辑缺陷,未校验区块链上交易详情中 valid 字段值是否为 true,导致“假充值”,用户未损失任何 USDT 却成功向交易所充值了 USDT,而且这些 USDT 可以正常进行交易。
很遗憾,经过笔者的一番查找发现,网上的很多资料都倾向于描述 USDT 的资本意义,而对于技术上原理的文章却寥寥无几。于是经过一些调查,笔者发现这个漏洞实际上并不能归因于 USDT 本身,而是交易所一方的问题导致。这种漏洞起因可以说非常简单,但一旦利用成功,造成的后果却难以估算。区块链开发中,各种低级错误,诸如大小写拼错导致的智能合约漏洞也是数不胜数(如:http://www.freebuf.com/vuls/175904.html),仅以本文抛砖引玉。本文将回答以下问题:
这次漏洞是怎么回事?受害者是谁?怎么防御?
ETH 在近一年来因为智能合约系统而受到了广泛地关注,大家可以用 5 分钟时间就发出自己的第一个代币,而代币的发行、转账则都依赖于智能合约,底层则是由 ETH 公链承载。事实上,远在 ETH 诞生之前,大家就想用比特币主链做点啥。其中,一个重要的概念被提出来了:染色币。具体来说,染色币是指在普通的比特币交易中附上一些信息,借助比特币底层基础设施来记录。然而,比特币官方开发组(Core)对这种方式颇有争议,将用于存储信息的 OP_RETURN
字段从 80 字节骤然缩小到了 40 字节( https://github.com/bitcoin/bitcoin/pull/3737)。
Omni Layer 也属于染色币,其核心思想是将 Omni Protocol 层的数据用某种方式写入比特币区块链中,目前在协议定义中有三种,Class A
、Class B
和 Class C
。
1. Class A - 这种方法是利用了比特币每个地址本质上都是大小为 20 bytes 的二进制字符串。先将数据分割成 20 bytes 为一组的数据块,然后使用 Base58 编码作为目标地址发送即可。当然这些被发送的 UTXO 将永远丢失。使用这项技术的还有 cryptograffiti。
2. Class B - 这种方法利用了比特币的 multisig ,即多签特性。发送一笔 1 of n
的多重签名交易(即,n 个地址中,任何一个地址签名即可花费这笔 UTXO)。当前版本的 Omni Layer 协议中最大支持 n = 3。
3. Class C - 这种方法则利用了OP_RETURN
操作码储存数据,目前来看,基本上所有的 Omni layer 层的交易都采用了这种方式。使用这项技术的还有 CoinSpark 。
Omni Protocol 能让用户发行自己的数字资产,其中资产编号为 31 的就是被广泛使用的 USDT 。而日常中,发送 USDT 使用的交易类型是 Simple Send
。
下面是对 Simple Send
交易的定义。
Simple Send
是一项将 Omni Layer 层中特定的数字资产从原地址转移到目标地址的操作,注意原地址和目标地址均使用比特币的地址。
可以看到上面的交易并没有任何的余额信息,这就是说,原地址的 Omni Layer 层数字资产,由 Omni Core 自己维护一个额外的账本并校验。
Omni core 的实现是遍历交易,自己维护一个 tally map,下图所示是 Omni core 正在逐笔扫描并构建账簿。
以随机抽取的某笔交易为例(b364bea8e4a9c9aeefda048df6f71dd62dc39d07a2286340e7c83c19b7ffd895):
输入 (1) 0.00500000 BTC
1NJdUJGCJgZ4YEwthHD1skdHPsr5TxHbpq 0.00500000
源地址输出 (3) 0.00498879 BTC
地址解析失败 - (解码) 0.00000000
1FoWyxwPXuj4C6abqwhjDWdz6D4PZgYRjA 0.00000546 转账目标
1NJdUJGCJgZ4YEwthHD1skdHPsr5TxHbpq 0.00498333 找零
其 OP_RETURN 字段为 6f6d6e69000000000000001f0000001ac68eac4b
6f6d6e69 0000 0000 0000001f 0000001ac68eac4b
6f6d6e69 => omni
0000 => 版本:0
0000 => 交易类型:Simple Send(0)
0000001f = 31 => 货币:USDT
0000001ac68eac4b = 115000388683 = 1150.00388683 => 转账金额
与 Omni 浏览器的内容一致。
而 Omni 浏览器 中的 Raw Data,则是由 Omni Core 自己根据扫描比特币区块链并重新构筑账本后输出的内容,这其中就有我们今天的主角 valid
了。
从背景知识中我们可以看出,实际上对于余额的校验是通过客户端来进行的,但很遗憾的是,与比特币不同,Omni Layer 并没有 UTXO 机制,这也就导致了无效交易也能被广播。
正常的转账流程如下:
用户发起 USDT 转账行为。
Omni Layer 的一个正常实现客户端生成交易。
客户端广播交易。
比特币区块链确认交易。
交易所确认交易数足够。
入帐交易所。
恶意攻击流程如下:
黑客发起一笔恶意转账行为。
黑客重新编译客户端,绕过余额检查,或采用其他方式生成恶意交易。
黑客广播恶意交易。
比特币区块链确认交易。
交易所确认交易数足够,没有校验交易合法性。
入帐交易所。
黑客提走资产。
以下几种情况均会导致交易状态不合法:
地址被冻结。
交易类型不允许。(目前只有当 property = 0 ,即为比特币时会出现这种情况)
交易金额超限,或小于零。
交易资产无效。
余额不足。
对交易合法性的定义在 omnicore/src/monicore/tx.cpp
的CMPTransaction::logicMath_SimpleSend
中。
下面我们亲自来构建一笔恶意交易。
为了方便起见,使用的是 Electrum 轻节点钱包。准备工具:
1. 一个有一点余额的比特币钱包 2. 一笔 Simple Send 的原始 tx 3. https://brainwalletx.github.io/#tx
步骤:
先使用 Electrum 发送一笔交易,目标地址随便写一个。
点击预览交易,将未签名的原始交易复制。
打开工具3
把原始交易粘贴上去,复制 JSON 交易。
在out
中 添加如下字段
然后把 JSON 粘贴回去,原始交易粘贴回来,签名广播(工具->加载交易->从文本),搞定。
https://www.omniexplorer.info/tx/6791b04ff752bffc9251910fa58bda5c85f28dabb4f2651dd1b9830037a2c1da
凭空产生的 USDT。
当然,之后这笔交易会被判定为 invalid。
本文首先对 USDT 进行了基础建设上的分析,然后讨论了这次漏洞出现的原因,最后,本文复现了被攻击场景。
实际上,这个逻辑漏洞的主要原因还是在交易所方面没有做好处理,面对这种涉及到金钱的事情,小心一些总是好的。
Tether 公司在以太坊上也发行了基于 ERC20 的 USDT,不知道会不会也出现类似的问题。
*本文原创作者:umiiii,本文属FreeBuf原创奖励计划,未经许可禁止转载