现在市面上出现的假充值的漏洞很多,比如前段时间出现的USDT的假充值问题,或者之前hackerone上曝光的coinbase中以太坊充值的问题。本报告主要探讨Qtum的问题。
这些假充值漏洞一般是由于接受虚拟货币支付的商家(如电商平台、交易所等)为了良好的支付体验,支持未确认交易充值的原因。出现漏洞的场景如下:
用户构造一个恶意交易,比如支付0.1个虚拟币,这个交易的交易费设置的很低
商家看到了该交易,但是此时该交易未被确认(矿工因为fee低没有打包该交易)
商家接受这个未被确认的交易,并支付实物
用户利用RBF技术将UTXO转回给自己
这里可以看到,一般出现这种情况都是因为商家接受了未确认交易造成的。
这种攻击核心的问题是:作为攻击者,如何去发起一个异常
的交易呢?
在bitcoin中,客户端和矿工会对交易的每个字段进行校验,如果伪造utxo或者交易费设置的非常低,会直接被P2P网络拒绝,因此也就进入不到mempool里被矿工选中。在bitcoin/qtum案例中,可以使用RFB来构造这种交易。
RFB即Replace-By-Fee,就是用一个较高的交易费替换一个已经存在的交易。替换的意思是新的交易会使用旧交易的input部分,但是这种情况并不是双花的问题,因为未被确认过的老交易其实是被替换掉了。因此这两个交易只会有一个交易被确认,并添加到区块链上。一般都是交易费给的高的那个交易。
这里其实会有个双花
的问题,场景是攻击者向商家支付0.1 BTC,并且商家支持未确认交易付款:
攻击者首先构造一个RBF交易tx1,转给商家0.1 BTC,这个交易的fee被设置的很低
tx1在mempool中,由于fee过低,所以迟迟不会被矿工选中打包
商家看到了这个交易,虽然该交易的状态是unconfirmed,但是认为该交易是有效的
商家把0.1 BTC对应的实物交割给攻击者
攻击者拿到实物之后,构造交易tx2,该交易的input使用tx1的,将tx1的0.1BTC的utxo再转给自己,然后设置一个高的fee,把这个交易广播出去
tx1被tx2替换,tx2被矿工确认了
攻击者仅仅付了一些交易费就获取到了实物,商家损失0.1 BTC
关于RFB,技术细节在这里,该改进协议在Bitcoin Core 0.12.0 被实现。
这里简单说下,一个交易如果任意一个input的nSequence字段设置为比(0xffffffff - 1 )小的数值,这个交易就是可以替换的交易,举个例子:
....
"vin": [
{
"txid": "0d158a5ec4dad1c6abda3eb24c13ee872a2468b2234e2b035f5f4a61cc908701",
"vout": 1,
"scriptSig": {
"asm": "304402201207cba81c284dfe30d87ded1bcdba267be1aa8753d0abecb7d18746ba6164d2022060566d9d19062bcb54a1f7dbf93711cf3ee14ea9d833ec78fba0f25ce313a2c9[ALL] 037f901bc1ca9601fdbdd464b55877134a859267e0e8863f089dde5a7a6acd2ab8",
"hex": "47304402201207cba81c284dfe30d87ded1bcdba267be1aa8753d0abecb7d18746ba6164d2022060566d9d19062bcb54a1f7dbf93711cf3ee14ea9d833ec78fba0f25ce313a2c90121037f901bc1ca9601fdbdd464b55877134a859267e0e8863f089dde5a7a6acd2ab8"
},
"sequence": 4294967293 // 这里是RBF交易的标识
}
],
....
当这个交易未被确认时,那么就可以发起另一个交易使用该交易的input来进行替换。
Qtum的代码基本和比特币的一致,所以用qtum来复现试试。
钱包使用qtum-electrum,在设置页面有一个选项可以打开RFB属性:
这里Propose Replace-By-Fee
设置为Always
。
然后先构造一个交易,比如这里的商家为:QN5j2oa9cywEBaTB3DUNXcjCGuiPttGJTWt
,自己的地址为QceiK7qDgnxruJ8HFTRiUDmfLybtsVZNAw
. 往商家的地址转0.002个qtum:
这里把fee设置的很低,设置为0.001。
交易大致如下:
看Outputs部分就知道这个交易就是往商户转账0.002数值的qtum,然后第二个输出是找零。
我们记录下这里input使用的UTXO,是f1671e8657b801e890bc69ea34e04a6d408f786524a72127f617aa018a5e51f0
, 索引是1,这里的余额是0.772763
Qtum:
我们来先构造第二个交易,使用qtum-cli:
# 创建原始交易
./qtum-cli createrawtransaction '[{"txid":"f1671e8657b801e890bc69ea34e04a6d408f786524a72127f617aa018a5e51f0","vout":1}]' '{"QceiK7qDgnxruJ8HFTRiUDmfLybtsVZNAw":0.770763}'
这个命令的含义是使用f1671e8657b801e890bc69ea34e04a6d408f786524a72127f617aa018a5e51f0:1
这个utxo作为输入,然后把这个utxo再转给自己,当然这里要自己去算一下手续费。
第一个交易中,我们设置的交易费是0.001,这里第二个交易我们设置为0.002就行了,计算交易2的输出的方法如下:
Output_in_tx_2 = 0.772763 - 0.002 = 0.770763
因此就得出了createrawtransaction命令中的数值。
然后再对这个交易进行签名:
# 签名交易
./qtum-cli signrawtransaction "0200000001f0515e8a01aa17f62721a72465788f406d4ae034ea69bc90e801b857861e67f10100000000ffffffff014c179804000000001976a914b006130cad3681eaf4182689e535c94399068cc288ac00000000"
输出如下:
{
"hex": "0200000001f0515e8a01aa17f62721a72465788f406d4ae034ea69bc90e801b857861e67f1010000006a47304402204c9c15714e33fd525025b435302bfa34fd604a3ff5836c699f1679b1c74db90a022041c13a858ed5215bfe1e6617c523f90c886749030f7ba39e6a4f3a45d2918dfe0121037f901bc1ca9601fdbdd464b55877134a859267e0e8863f089dde5a7a6acd2ab8ffffffff014c179804000000001976a914b006130cad3681eaf4182689e535c94399068cc288ac00000000",
"complete": true
}
首先我们把第一个交易广播出去,调用以下命令即可:
./qtum-cli sendrawtransaction "hexstring"
或者在钱包端直接去广播也行。
在钱包的交易池里可以看到这个交易没有被确认,标记为了Replaceable
,表明这个交易可以被RFB。
然后发现这个交易已经在qtum浏览器中可以看到了:
由于fee设置的很低,通常如果不进行RFB操作的话,这个交易被确认要等5-6 mins(网络越拥堵越好),这时如果商家支持未确认交易支付的话就可以付款成功了。
然后我们把第二个交易发送出去:
就可以在钱包交易池中看到,第一个交易被第二个交易替换了,而第二个交易详情如下:
这里就是把第一个交易里输入的的utxo再转给自己,只不过手续费为0.002 Qtum,是第一个交易的两倍。这时候我们就只剩一个交易了,即第二个交易替换了第一个交易:
在Qtum浏览器上看是这样的,这时候再刷新第一个交易的页面,发现该交易已经没有了,但是我们的目的达到了,即在商家端是支付成功了,交割实物走人。整个过程我们只耗费了0.002个qtum的手续费。
总结下,Qtum假充值的整个过程如下:
发起交易1,支付0.02 qtum支付给 商家,手续费为0.001
商家看到了交易,虽然没有被确认,但是也看作是支付成功
然后在交易1没有被确认的间隙下,立刻发起交易2,将交易1中输入部分的utxo 支付给自己,手续费为0.002
交易1不复存在,交易2被确认写入链上,完成攻击
总的来说,这种假充值的漏洞,一般公链是不会背锅的,区块链的共识使得交易无法原子操作。只能依赖DApp端来对交易进行确认,不过随着后续公链出块时间越来越快,这种攻击可能就难以发起了。
(1) https://explorer.qtum.org/
(2) https://qtumwallet.org/
(3) https://github.com/petertodd/replace-by-fee-tools
(4) https://medium.com/@overtorment/bitcoin-replace-by-fee-guide-e10032f9a93f