门罗币(XMR)锁定转账攻击细节分析

 

By: ISME@SlowMist Team

近日据慢雾区情报显示,针对门罗币(XMR)转账锁定攻击在多个交易所出现,慢雾安全团队在收到情报第一时间进行分析跟进,本着负责任披露的原则我们第一时间在慢雾区进行了预警并为我们所服务的客户进行了及时的情报同步以及协助检测和修复。如有其他需要提供验证和检测服务欢迎联系慢雾安全团队。

 

攻击步骤

0x01:通过 monero-wallet-cli 输入密码登录钱包
0x02:通过命令发送锁定交易
0x03:转账完成,交易所未进行锁定交易(locked_transfer)检测,接收到被设置锁定区块高度才能解锁的币(可以理解为锁定了指定时间)。
0x04:恶意用户立即提币走人,留下交易所一脸懵逼。

 

造成影响

首先该攻击不会导致交易所任何资金损失,但是会锁定了交易所 XMR 流动性。

极端情况举例:如果交易所收到的都是需要锁定一年甚至更多年的门罗币则会导致一年内用户来提币的时候无币可以提(只能去购买额外的币来给用户提取)。

 

关于 locked_transfer 命令

monero-wallet-cli 关于 locked_transfer 命令解释如下:

locked_transfer [index=<N1>[,<N2>,…]] [<priority>] [<ring_size>] (<URI> | <addr> <amount>) <lockblocks> [<payment_id (obsolete)>]

转账命令:

locked_transfer FromAddress ToAddress 0.0101 20000

FromAddress:发送地址(一般为攻击者钱包地址)

ToAddress:接收地址(一般为交易所钱包地址)

0.0101:为转账金额

20000:为锁定区块数

 

如何防护

一般交易所会通过 get_transfers RPC 接口来解析 XMR 交易检测充值是否到账,在进行解析的时候只需要对 unlock_time 字段进行判断是否大于 0 则可以进行有效检测。

注:unlock_time 为 int 类型,如果大于 0 则意味着该交易有锁定区块,为恶意交易可以不予确认到账。为了避免充值不予到账损害“用户”利益可以进行另外一种处理:判断锁定区块是否到达,如果未到达则不予入账。

 

所有受影响 RPC 接口

(1)get_transfer
(2)get_bulk_payments
(3)show_transfer
(4)get_payments

同理:在其他地方使用了如上四个接口的地方也需要对 unlock_time 字段进行判断是否大于 0 ,大于 0 则不予充值到账。

该问题之前在 HackerOne 也有被白帽子提过漏洞赏金,其中门罗官方回复:

文章链接:

https://hackerone.com/reports/417515

附:以下内容为官方文档摘录

get_transfers

Returns a list oftransfers.
Alias: None.
Inputs:

 in - boolean;     (Optional) Include incoming transfers.
 out - boolean;     (Optional) Include outgoing transfers.
 pending - boolean;     (Optional) Include pending transfers.
 failed - boolean;     (Optional) Include failed transfers.
 pool - boolean;     (Optional) Include transfers from the daemon's transaction pool.
 filter_by_height - boolean;     (Optional) Filter transfers by block height.
 min_height - unsigned     int; (Optional) Minimum block height to scan for transfers, if filtering     by height is enabled.
 max_height - unsigned     int; (Opional) Maximum block height to scan for transfers, if filtering by     height is enabled (defaults to max block height).
 account_index - unsigned     int; (Optional) Index of the account to query for transfers. (defaults to     0)
 subaddr_indices - array of     unsigned int; (Optional) List of subaddress indices to query for     transfers. (Defaults to empty - all indices)

Outputs:

 in array of     transfers:
     address - string;      Public address of the transfer.
     amount - unsigned      int; Amount transferred.
     confirmations -      unsigned int; Number of block mined since the block containing this      transaction (or block height at which the transaction should be added to      a block if not yet confirmed).
     double_spend_seen -      boolean; True if the key image(s) for the transfer have been seen before.
     fee -      unsigned int; Transaction fee for this transfer.
     height -      unsigned int; Height of the first block that confirmed this transfer (0      if not mined yet).
     note - string;      Note about this transfer.
     payment_id - string;      Payment ID for this transfer.
     subaddr_index - JSON      object containing the major & minor subaddress index:
         major -       unsigned int; Account index for the subaddress.
         minor -       unsigned int; Index of the subaddress under the account.
     suggested_confirmations_threshold -      unsigned int; Estimation of the confirmations needed for the transaction      to be included in a block.
     timestamp -      unsigned int; POSIX timestamp for when this transfer was first confirmed      in a block (or timestamp submission if not mined yet).
     txid - string;      Transaction ID for this transfer.
     type - string;      Transfer type: "in"
     **unlock_time - unsigned int; Number of blocks until transfer is safely      spendable.**
 out array of     transfers (see above).
 pending array of     transfers (see above).
 failed array of     transfers (see above).
 pool array of transfers (see above).

Example:
$ curl -X POST http://127.0.0.1:18082/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"get_transfers","params":{"in":true,"account_index":1}}' -H 'Content-Type: application/json'
{
"id": "0",
"jsonrpc": "2.0",
"result": {
"in": [{
"address": "77Vx9cs1VPicFndSVgYUvTdLCJEZw9h81hXLMYsjBCXSJfUehLa9TDW3Ffh45SQa7xb6dUs18mpNxfUhQGqfwXPSMrvKhVp",
"amount": 200000000000,
"confirmations": 1,
"double_spend_seen": false,
"fee": 21650200000,
"height": 153624,
"note": "",
"payment_id": "0000000000000000",
"subaddr_index": {
"major": 1,
"minor": 0
},
"suggested_confirmations_threshold": 1,
"timestamp": 1535918400,
"txid": "c36258a276018c3a4bc1f195a7fb530f50cd63a4fa765fb7c6f7f49fc051762a",
"type": "in",
"unlock_time": 0
}]
}
}

get_payments

Get a list ofincoming payments using a given payment id.
Alias: None.
Inputs:

 payment_id - string;     Payment ID used to find the payments (16 characters hex).

Outputs:

 payments - list of:
     payment_id - string;      Payment ID matching the input parameter.
     tx_hash - string;      Transaction hash used as the transaction ID.
     amount - unsigned      int; Amount for this payment.
     block_height -      unsigned int; Height of the block that first confirmed this payment.
     **unlock_time - unsigned int; Time (in block height) until      this payment is safe to spend.**
     subaddr_index -      subaddress index:
     major - unsigned       int; Account index for the subaddress.
     minor -       unsigned int; Index of the subaddress in the account.
     address - string;      Address receiving the payment; Base58 representation of the public keys.

Example:
$ curl -X POST http://127.0.0.1:18082/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"get_payments","params":{"payment_id":"60900e5603bf96e3"}}' -H 'Content-Type: application/json'
{
"id": "0",
"jsonrpc": "2.0",
"result": {
"payments": [{
"address": "55LTR8KniP4LQGJSPtbYDacR7dz8RBFnsfAKMaMuwUNYX6aQbBcovzDPyrQF9KXF9tVU6Xk3K8no1BywnJX6GvZX8yJsXvt",
"amount": 1000000000000,
"block_height": 127606,
"payment_id": "60900e5603bf96e3",
"subaddr_index": {
"major": 0,
"minor": 0
},
"tx_hash": "3292e83ad28fc1cc7bc26dbd38862308f4588680fbf93eae3e803cddd1bd614f",
"unlock_time": 0
}]
}
}

get_bulk_payments

Get a list ofincoming payments using a given payment id, or a list of payments ids, from agiven height. This method is the preferred method over get_paymentsbecause it has the same functionality butis more extendable. Either is fine for looking up transactions by a singlepayment ID.
Alias: None.
Inputs:

 payment_ids - array     of: string; Payment IDs used to find the payments (16 characters hex).
 min_block_height - unsigned     int; The block height at which to start looking for payments.

Outputs:

 payments - list of:
     payment_id - string;      Payment ID matching one of the input IDs.
     tx_hash - string;      Transaction hash used as the transaction ID.
     amount -      unsigned int; Amount for this payment.
     block_height -      unsigned int; Height of the block that first confirmed this payment.
     unlock_time - unsigned int; Time (in block height) until      this payment is safe to spend.
     subaddr_index - subaddress      index:
         major -       unsigned int; Account index for the subaddress.
         minor -       unsigned int; Index of the subaddress in the account.
         address - string;      Address receiving the payment; Base58 representation of the public keys.

Example:
$ curl -X POST http://127.0.0.1:18082/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"get_bulk_payments","params":{"payment_ids":["60900e5603bf96e3"],"min_block_height":"120000"}}' -H 'Content-Type: application/json'
{
"id": "0",
"jsonrpc": "2.0",
"result": {
"payments": [{
"address": "55LTR8KniP4LQGJSPtbYDacR7dz8RBFnsfAKMaMuwUNYX6aQbBcovzDPyrQF9KXF9tVU6Xk3K8no1BywnJX6GvZX8yJsXvt",
"amount": 1000000000000,
"block_height": 127606,
"payment_id": "60900e5603bf96e3",
"subaddr_index": {
"major": 0,
"minor": 0
},
"tx_hash": "3292e83ad28fc1cc7bc26dbd38862308f4588680fbf93eae3e803cddd1bd614f",
"unlock_time": 0
}]
}
}

get_transfer_by_txid

Show informationabout a transfer to/from this address.
Alias: None.
Inputs:

 txid - string;     Transaction ID used to find the transfer.
 account_index - unsigned     int; (Optional) Index of the account to query for the transfer.

Outputs:

 transfer - JSON     object containing payment information:
     address - string;      Address that transferred the funds. Base58 representation of the public      keys.
     amount -      unsigned int; Amount of this transfer.
     confirmations -      unsigned int; Number of block mined since the block containing this      transaction (or block height at which the transaction should be added to      a block if not yet confirmed).
     destinations - array      of JSON objects containing transfer destinations:
         amount -       unsigned int; Amount transferred to this destination.
         address -       string; Address for this destination. Base58 representation of the public       keys.
     double_spend_seen -      boolean; True if the key image(s) for the transfer have been seen before.
     fee -      unsigned int; Transaction fee for this transfer.
     height -      unsigned int; Height of the first block that confirmed this transfer.
     note - string;      Note about this transfer.
     payment_id - string;      Payment ID for this transfer.
     subaddr_index - JSON      object containing the major & minor subaddress index:
         major -       unsigned int; Account index for the subaddress.
         minor -       unsigned int; Index of the subaddress under the account.
     suggested_confirmations_threshold -      unsigned int; Estimation of the confirmations needed for the transaction      to be included in a block.
     timestamp -      unsigned int; POSIX timestamp for the block that confirmed this transfer      (or timestamp submission if not mined yet).
     txid - string;      Transaction ID of this transfer (same as input TXID).
     type - string;      Type of transfer, one of the following: "in", "out",      "pending", "failed", "pool"
     unlock_time - unsigned int; Number of blocks until      transfer is safely spendable.

Example:
$ curl -X POST http://localhost:18082/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"get_transfer_by_txid","params":{"txid":"c36258a276018c3a4bc1f195a7fb530f50cd63a4fa765fb7c6f7f49fc051762a"}}' -H 'Content-Type: application/json'
{
"id": "0",
"jsonrpc": "2.0",
"result": {
"transfer": {
"address": "55LTR8KniP4LQGJSPtbYDacR7dz8RBFnsfAKMaMuwUNYX6aQbBcovzDPyrQF9KXF9tVU6Xk3K8no1BywnJX6GvZX8yJsXvt",
"amount": 300000000000,
"confirmations": 1,
"destinations": [{
"address": "7BnERTpvL5MbCLtj5n9No7J5oE5hHiB3tVCK5cjSvCsYWD2WRJLFuWeKTLiXo5QJqt2ZwUaLy2Vh1Ad51K7FNgqcHgjW85o",
"amount": 100000000000
},{
"address": "77Vx9cs1VPicFndSVgYUvTdLCJEZw9h81hXLMYsjBCXSJfUehLa9TDW3Ffh45SQa7xb6dUs18mpNxfUhQGqfwXPSMrvKhVp",
"amount": 200000000000
}],
"double_spend_seen": false,
"fee": 21650200000,
"height": 153624,
"note": "",
"payment_id": "0000000000000000",
"subaddr_index": {
"major": 0,
"minor": 0
},
"suggested_confirmations_threshold": 1,
"timestamp": 1535918400,
"txid": "c36258a276018c3a4bc1f195a7fb530f50cd63a4fa765fb7c6f7f49fc051762a",
"type": "out",
"unlock_time": 0
}
}
}

点击查看官方文档

(完)