A Post Mortem of The Multiple Counting Bug

Posted by: dEBRUYNE

This blog sets out the multiple counting bug, of which two variants existed. The goal of this blog post is to provide a detailed explanation of aforementioned bug, how it was used to exploit services, merchants, and exchanges, and how it was handled by the Monero (dev) community.

The multiple counting bug, of which two variants existed, was introduced in conjunction with the subaddress feature, which required a different (code) structure of the transaction public key. The first variant of the bug is basically that the code didn't impose a check to guard against duplicate public keys. Therefore, an attacker could create a transaction in which the transaction public key was included multiple times, thereby duplicating the particular transaction public key. As a result, the receiving wallet would report that it had received x times (where x is an integer that represents the number of identical transaction public keys) the amount it had actually received. All commands that report incoming transactions (e.g. show_transfers (CLI), get_transfers (RPC)) were affected. The balance, however, was not affected, i.e., the wallet would still report the balance properly. Alas, most exchanges utilize the get_transfers or get_payments wallet RPC command, which would, in case of duplicate transaction public keys, return an erroneous amount.

Unfortunately, this variant of the bug was fairly trivial to exploit, i.e., an attacker simply had to append add_tx_pub_key_to_extra(tx, txkey_pub); in src/cryptonote_core/cryptonote_tx_utils.cpp and their transaction public key would, after recompiling the code, be duplicated. Practically speaking this works as follows. The attacker starts with appending src/cryptonote_core/cryptonote_tx_utils.cpp , say, thrice^1, thereby creating four identical public transactions keys for their transactions. Subsequently, they send a transaction of, say, 1 XMR to an exchange they seek to exploit. The exchange will, most likely, credit the attacker with 4 XMR. The hacker, thereafter, withdraws this 4 XMR, thereby basically robbing the exchange of 3 XMR. If the exchange does not regularly check the balance against the sum of incoming and outgoing transfers or checks the hotwallet for any abnormalities, the hacker can basically repeat aforementioned process until the Monero hot wallet is emptied or, worst case scenario, all Monero funds are depleted.

The second variant of the bug, of which a detailed report can be found on HackerOne, entails the code not imposing a check against dummy transaction public keys. Therefore, a hacker could utilize the alternative transaction public keys feature to trick the wallet into scanning the outputs in a transaction twice. As a result, the receiving wallet would report that it had received two times the amount it had actually received. Similar to the first variant of the bug, the balance was not affected.

The first variant was first reported in this issue on Github and was promptly fixed by moneromooo in this pull request. Unfortunately, however, the severity of the bug was underestimated until (i) an exchange got exploited via a fork of Monero and (ii) a security researcher (jagerman) on HackerOne provided an elaborate report on how to utilize this bug to steal funds from exchanges. The second variant was reported by phiren on HackerOne and quickly fixed in this pull request. Both patches got merged by fluffypony and were included in the v0.12.3.0 release.

After v0.12.3.0 was tagged, I (and others) privately notified as many exchanges, services, and merchants as possible. Obviously, this is not the preferred method, as it (i) invariably excludes organizations that I (and others) personally do not have contact with, but are an essential part of the Monero ecosystem and (ii) may invoke a view of preferential treatment. In addition, the bug should have been reported on the public mailing list, but, alas, wasn't. An oversight which should be learnt from. Furthermore, we, as the Monero community, should seek improvements that would streamline this vulnerability report process (i.e. reporting a critical vulnerability to exchanges, services, and merchants). A "private" mailing list to which only services, merchants, and exchange are able to subscribe may be in line with this notion. Some kind of verification of subscribers would be required though, which could be a tedious process. Although, it would probably be more secure than a public mailing list to which a clever attacker would undoubtedly subscribe.

In sum, a critical bug in the wallet software, of which the severity was initially significantly underestimated, allowed an attacker to steal funds from organizations present in the Monero ecosystem. Fortunately, the bug was confined to the accounting functionality of the wallet software, and thus the protocol and coin supply were not affected. It's imperative, however, that we learn from this event and seek improvements that would significantly mitigate the impact in case a similar bug is discovered in the future. Furthermore, this event is an effective reminder that cryptocurrency and the corresponding software are still in its infancy and thus quite prone to (critical) bugs. As such, it would be prudent for organizations to include as many sanity checks as possible (e.g. a check to verify the sum of transfers against the balance). In addition, the Monero dev community is investigating the feasibility of adding such a check to the RPC wallet.

Notes: