Analysis of the details of the Monero (XMR) lock transfer attack

Analysis of the details of the Monero (XMR) lock transfer attack

 

By: ISME @SlowMist Team

Recently, according to the intelligence of the SlowMist Zone, a lock-in attack on Monero (XMR) transfers has occurred on multiple exchanges. The SlowMist security team will analyze and follow up as soon as the intelligence is received. We are based on the principle of responsible disclosure. In the slow fog area, early warning was carried out and timely intelligence synchronization and assistance in detection and repair were carried out for the customers we serve. If you need to provide verification and testing services, please contact the SlowMist security team.

 

Attack steps

0x01: Enter the password to log in to the wallet through monero-wallet-cli
0x02: Send the lock transaction through the command
0x03: The transfer is completed, the exchange has not performed the locked_transfer detection, and the currency that can be unlocked by setting the lock block height is received (understandable To lock the specified time).
0x04: The malicious user immediately withdrew the coin and left, leaving the exchange with a bewildered look.

 

Make an impact

First of all, the attack will not cause any loss of funds on the exchange, but it will lock the XMR liquidity of the exchange.

An extreme case example: If the exchange receives all Monero coins that need to be locked for one year or more, it will cause users to withdraw coins within one year and no coins can be withdrawn (only to buy extra coins to give users extract).

 

About the locked_transfer command

monero-wallet-cli explains the locked_transfer command as follows:

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

Transfer order:

locked_transfer FromAddress ToAddress 0.0101 20000

FromAddress: send address (usually the address of the attacker's wallet)

ToAddress: Receiving address (usually exchange wallet address)

0.0101: is the transfer amount

20000: is the number of locked blocks

 

How to protect

Generally, exchanges will use the get_transfers RPC interface to parse the XMR transaction to detect whether the recharge has arrived. When parsing, it only needs to determine whether the unlock_time field is greater than 0 for effective detection.

Note: unlock_time is an int type, if it is greater than 0, it means that the transaction has a locked block, and it is a malicious transaction that can not be confirmed. In order to avoid the damage to the interests of the "user" if the recharge is not received, another process can be carried out: judge whether the locked block has arrived, and if it does not arrive, it will not be credited to the account.

 

All affected RPC interfaces

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

The same reason: where the above four interfaces are used in other places, it is also necessary to judge whether the unlock_time field is greater than 0. If it is greater than 0, it will not be recharged to the account.

This issue has also been mentioned in HackerOne for bug bounty before, among which Monroe s official reply:

Article link:

hackerone.com/reports/417...

Attachment: The following content is an excerpt from the official document

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 of incoming 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 of incoming payments using a given payment id, or a list of payments ids, from agiven height. This method is the preferred method over get_payments because 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 information about 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
}
}
}

Click to view official documents