RBF技术实现“双花漏洞”研究

0x00 概述

现在市面上出现的假充值的漏洞很多,比如前段时间出现的USDT的假充值问题,或者之前hackerone上曝光的coinbase中以太坊充值的问题。本报告主要探讨Qtum的问题。

0x01 假充值攻击过程

这些假充值漏洞一般是由于接受虚拟货币支付的商家(如电商平台、交易所等)为了良好的支付体验,支持未确认交易充值的原因。出现漏洞的场景如下:

  • 用户构造一个恶意交易,比如支付0.1个虚拟币,这个交易的交易费设置的很低

  • 商家看到了该交易,但是此时该交易未被确认(矿工因为fee低没有打包该交易)

  • 商家接受这个未被确认的交易,并支付实物

  • 用户利用RBF技术将UTXO转回给自己

这里可以看到,一般出现这种情况都是因为商家接受了未确认交易造成的。

这种攻击核心的问题是:作为攻击者,如何去发起一个异常的交易呢?

在bitcoin中,客户端和矿工会对交易的每个字段进行校验,如果伪造utxo或者交易费设置的非常低,会直接被P2P网络拒绝,因此也就进入不到mempool里被矿工选中。在bitcoin/qtum案例中,可以使用RFB来构造这种交易。

 

0x02 RBF技术

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来进行替换。

 

0x03 构造交易

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
}

 

0x03 广播交易复现

首先我们把第一个交易广播出去,调用以下命令即可:

./qtum-cli sendrawtransaction "hexstring"

或者在钱包端直接去广播也行。

在钱包的交易池里可以看到这个交易没有被确认,标记为了Replaceable,表明这个交易可以被RFB。

然后发现这个交易已经在qtum浏览器中可以看到了:

由于fee设置的很低,通常如果不进行RFB操作的话,这个交易被确认要等5-6 mins(网络越拥堵越好),这时如果商家支持未确认交易支付的话就可以付款成功了。

然后我们把第二个交易发送出去:

就可以在钱包交易池中看到,第一个交易被第二个交易替换了,而第二个交易详情如下:

这里就是把第一个交易里输入的的utxo再转给自己,只不过手续费为0.002 Qtum,是第一个交易的两倍。这时候我们就只剩一个交易了,即第二个交易替换了第一个交易:

在Qtum浏览器上看是这样的,这时候再刷新第一个交易的页面,发现该交易已经没有了,但是我们的目的达到了,即在商家端是支付成功了,交割实物走人。整个过程我们只耗费了0.002个qtum的手续费。

 

0x04 总结

总结下,Qtum假充值的整个过程如下:

  • 发起交易1,支付0.02 qtum支付给 商家,手续费为0.001

  • 商家看到了交易,虽然没有被确认,但是也看作是支付成功

  • 然后在交易1没有被确认的间隙下,立刻发起交易2,将交易1中输入部分的utxo 支付给自己,手续费为0.002

  • 交易1不复存在,交易2被确认写入链上,完成攻击

总的来说,这种假充值的漏洞,一般公链是不会背锅的,区块链的共识使得交易无法原子操作。只能依赖DApp端来对交易进行确认,不过随着后续公链出块时间越来越快,这种攻击可能就难以发起了。

 

Reference

(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

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页