Vulnerability Disclosure: Wasting ViaBTC's 60 EH/s hashrate by sending a P2P message

Sloppy Pseudo Verification (SPV) mining
Wednesday, March 20, 2024

In January, while investigating a misbehaving client on the Bitcoin P2P network, I found a vulnerability in ViaBTC’s, the fourth largest Bitcoin mining pool, SPV mining code that allowed a remote attacker to waste ViaBTC’s 60 EH/s hashrate by sending a single, crafted Bitcoin P2P message. I responsibly disclosed this to ViaBTC, and they awarded a bug bounty of 2000 USDT.


Improper Input Validation in the SPV mining module of the ViaBTC mining server (not fixed on GitHub, fixed in a closed-source version) allows a remote attacker to waste the pools hashrate by letting it mine on an old block (i.e. DoS) by sending a modified, old block via the P2P network.

ViaBTC’s SPV mining module and Bitcoin P2P client, bitpeer, did not check the merkle root of received blocks, allowing an attacker to make ViaBTC’s miners mine on an old block by sending a modified previous block. While ViaBTC fixed this vulnerability in their systems, the open source viabtc_mining_server is still affected.

ViaBTC Mining Server

In March 2021, ViaBTC published a version of its mining server on GitHub. While this repository hasn’t been updated since summer 2021, the underlying software, likely with a few modifications and patches, is still in use by ViaBTC. The mining server is made up of multiple modules. For the disclosed vulnerability, the interesting modules are the bitpeer module and the jobmaster module. The jobmaster module is responsible for producing mining jobs by talking to a Bitcoin Core node. The bitpeer module is a Bitcoin P2P client and connects to multiple other nodes on the Bitcoin P2P network. The bitpeer client broadcast ViaBTC’s newly found blocks to Bitcoin nodes and notifies the jobmaster about new blocks received over the network.

Infrastructure diagram of the ViaBTC mining server
Infrastructure diagram of the ViaBTC mining server. Based on this overview.

SPV mining

Mining pools want to minimize the time they spent mining on an old block, when a new one has been found by another pool. To quickly switch to a new block, ViaBTC’s bitpeer module sends details, like the new block height and the block hash, to the jobmaster. If the new block height is current block height + 1, the jobmaster switches to SPV (Simple Payment Verification) mining mode. In SPV mining mode, a mining job is issued to miners without having validated the new block. Without having validated the transactions in the previous block, the SPV mining job can only be an empty block (only a coinbase transaction).

Vulnerability

The process_block() function in bp_peer.c is called when a new block message arrives over the P2P network. In this function, it’s first checked that the message contains a block header with enough proof-of-work (higher than the current difficulty requirement). Then, the BIP-34 block height is extracted from the coinbase. If this height is larger than the best know height, the block is send to the jobmaster by calling send_block_nitify().

Here, checking that the block header has a valid and enough proof-of-work to be an SPV-valid block defends against most attacks. The cost of mining a block header with enough proof-of-work is high. For an attacker, it does not make sense to spend that hashrate to attack ViaBTC with an otherwise invalid block. A rational actor would use the hashrate to mine for them selves.

However, ViaBTC only checks the header. They don’t verify that the transactions in the block match the merkle root in the header. This means, the coinbase transaction and thus the BIP-34 block height in the coinbase transaction can be modified. Since they use the height in the coinbase to determine if they should SPV mine, the pool can be tricked into mining on a valid, but old, header with an arbitrary coinbase transaction attached.

Exploit

To exploit this vulnerability, an attacker needs to send a crafted block message to a ViaBTC bitpeer client. ViaBTC runs multiple instances of bitpeer in different data centers distributed around the globe. Each instance opens connections to multiple listening nodes on the P2P Bitcoin network. On my nodes I counted zero to two connections from bitpeer instances per node. In total, I saw seven different IP addresses, which indicates there are at least seven instances running. These bitpeer peers can be detected as they always use the same fake user agent of /Satoshi:0.19.0.1/ and only set the WITNESS service flag. The IP addresses always belong to AWS-owned IP ranges. Connections from bitpeer instances can, for example, be listed with the following command.

$ bitcoin-cli getpeerinfo | jq '.[] | select(.subver == "/Satoshi:0.19.0.1/" and .services == "0000000000000008")'

A block header used for this attack should be from the same difficulty adjustment period to meet the difficulty requirements. It does not matter which coinbase transaction is chosen. The BIP-34 height in the coinbase input needs to be current network height + 1. To send this block to a bitpeer instance, the recently introduced, test-only sendmsgtopeer Bitcoin Core RPC call can be used.

$ bitcoin-cli sendmsgtopeer <peer-id> "block" <block hex>

Local and remote verification

To make sure the vulnerability is practicably exploitable before I disclose it, and not only a theoretical issue, I tested it against a local test setup using the viabtc_mining_server code available on GitHub.

I build, installed, and configured a bitpeer and jobmaster instance to use a local Bitcoin Core node. The bitpeer instance was configured to connect to a second local Bitcoin Core node via P2P. To be able to start the bitpeer and jobmaster instances I needed to comment out some startup actions for services I was unable to configure (mostly due to missing documentation). Once set up, I constructed a block composed of a real block header and a coinbase transaction encoding a height a few hundred blocks into the future. I send the block to the bitpeer instance, and it notified the jobmaster about the new block. I couldn’t tell from my patched jobmaster instance if it would have switched to SPV mode as it crashed due not being configured correctly. This made it hard to verify the vulnerability locally.

At this point I wasn’t sure if this vulnerability could even be exploited against ViaBTC. Surely they are running an updated and maintained version of their mining server? I contemplated trying to verify the vulnerability against ViaBTC’s production mining pool. As far as I could tell, there is no ViaBTC testnet pool available. I knew that ViaBTC would, by default, only SPV mine for 30 seconds, which made the possible damage done manageable: about $1000 of lost revenue per attack. If the vulnerability could be exploited, I could supply ViaBTC with log timestamps and the information about the block I send. Showing that the vulnerability can be exploited against their production pool increases the chances of them taking this seriously. I feared they wouldn’t take this seriously if I only a reported a vulnerability in unmaintained version of their mining server they uploaded to GitHub three years ago1.

I ultimately decided to go ahead with trying to verify the vulnerability against the production pool. I attempted this only once, while taking great care not to unnecessarily harm ViaBTC or it’s customers.

I choose block 826284 mined a few hours earlier. This block has only a coinbase transaction and is comparatively quite small, which made it easy to handle in a text editor and on the command line. However, vulnerability can be exploited with larger blocks just as well. At the time, block 826337 was the most recent block. My Bitcoin Core node saw it at 2024-01-19 00:55:46. The 53 block difference between these blocks was big enough to make it clear to ViaBTC that a successful exploitation isn’t just a small reorg.

Block 826284 encodes an original block height of ac9b0c in the coinbase transaction. I set the height to e29b0c (826338; current height of 826337 + 1) in the coinbase using a text editor. Note that the BIP34 height in the coinbase is encoded in little-endian.

  original  ac9b0c  -  826284  
  current   e19b0c  -  826337  
  modified  e29b0c  -  826338  

The following shows the modified block 826284. The only difference to the original block is that the coinbase height has been modified from ac9b0c to e29b0c.

0000cd2765e111fa8f2870ae4fdaa564907e501ac5ab12d0cab6020000000000000000007908d44825dc7bc91fb883fa5b2
083f0f95641aa4ac518bb968c2f29ae61a00f4357a96569d80317397f083301010000000100000000000000000000000000
00000000000000000000000000000000000000ffffffff6403█e29b0c█2cfabe6d6d654b01255ebb409c165395395b3f3c2
301f032f53df45cba5ed5d266dc2c786010000000f09f909f092f4632506f6f6c2f73000000000000000000000000000000
00000000000000000000000000000000000000000500ae970800000000000422020000000000001976a914c6740a12d0a7d
556f89782bf5faf0e12cf25a63988ac1ebc4025000000001976a914c825a1ecf2a6830c4401620c3a16f1995057c2ab88ac
00000000000000002f6a2d434f524501a21cbd3caa4fe89bccd1d716c92ce4533e4d4733bdb2a04b4ccf74792cc6753c27c
5fd5f1d6458bf00000000000000002c6a4c2952534b424c4f434b3acd2e3ba1354794d09aabccd650c2155ae16cd9830cc9
b0d57aecd423005ba3a64940a53f

To track which previous block ViaBTC mines on, I set up a patched version of achow101’s stratum-watcher. This connects to the ViaBTC stratum server and listens for new mining jobs. My patch prints which previous block is specified in the mining jobs. If ViaBTC sends out a new mining job with the block hash of 826284, I’d know that ViaBTC is vulnerable.

I send the modified block to one of my bitpeer peers using the sendmsgtopeer RPC call on 2024-01-19 at 00:56:28 UTC. My Bitcoin Core node with net debug logging showed that the block was sent: sending block (409 bytes).

At the same time, I saw that the ViaBTC stratum servers I was connected to send out a new mining job switching to mining on block 0000000000000000000231524f6ba483c6d6e84b68622ec7128a7269bcb9a9d8. This meant I had successfully confirmed that the vulnerability is exploitable against the production ViaBTC mining pool. All other pools kept mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017.

[..]
00:56:25,877: btc.f2pool.com 	mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
00:56:25,924: btc-eu.f2pool.com mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
█ 00:56:28,678: btc.viabtc.io 	mining on 0000000000000000000231524f6ba483c6d6e84b68622ec7128a7269bcb9a9d8
█ 00:56:28,687: btc.viabtc.com 	mining on 0000000000000000000231524f6ba483c6d6e84b68622ec7128a7269bcb9a9d8
█ 00:56:28,699: btc.viabtc.com 	mining on 0000000000000000000231524f6ba483c6d6e84b68622ec7128a7269bcb9a9d8
█ 00:56:28,904: btc.viabtc.io 	mining on 0000000000000000000231524f6ba483c6d6e84b68622ec7128a7269bcb9a9d8
00:56:32,710: solo.ckpool.org 	mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
00:56:35,854: stratum.kano.is 	mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
[..]

Exactly 30 seconds later (ViaBTC’s default SPV-mining timeout), ViaBTC switched back to the correct previous block 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017. In these 30 seconds, ViaBTC’s miners wasted about 1.8 sextillion hashes (1.8×10²¹ or 1.8 zeta hashes) mining on an old block. No block was found during these 30 seconds by neither ViaBTC nor another pool. If a ViaBTC miner had found an empty block, the block would have been invalid2 as the height commitment in the coinbase would been 826338 and not 826285 as expected with the previous block hash in the header.

00:56:49,503: [..]slushpool.com mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
00:56:51,422: [..]slushpool.com mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
█ 00:56:58,645: btc.viabtc.io 	mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
█ 00:56:58,677: btc.viabtc.com 	mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
█ 00:56:58,721: btc.viabtc.com 	mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
█ 00:56:58,899: btc.viabtc.io 	mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
00:57:02,589: solo.ckpool.org 	mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017
00:57:05,867: stratum.kano.is 	mining on 0000000000000000000005575548ec79ea5afb112f91422e12aad67080fda017

I stopped digging further at this point and responsibly disclosed the vulnerability to ViaBTC.

Impact

The vulnerability has a denial-of-service impact with a measurable associated business cost. By default, ViaBTC miners are paid according to PPS+ (Pay Per Share Plus): this means, miners are paid even if ViaBTC does not find a block or isn’t rewarded for finding a block. When the vulnerability was discovered, ViaBTC had around 12% of the network hashrate. Assuming 144 blocks per day, that are 12 blocks per day found by ViaBTC. With each block worth at least 6.25 BTC, that’s 75 BTC or $3M USD per day at $40k USD/BTC. This is ~$35 USD per second. Exploiting the vulnerability once for 30 seconds costs ViaBTC at least $1000 USD.

When a bitpeer instance sends a SPV mining notification, it increases the best known height and does not notify for the same height again. This means, a single bitpeer instance can only be used once per block to exploit the vulnerability. However, since there are at least seven bitpeer instances running, in theory, a malicious actor (e.g., a competing mining pool) could have exploited the vulnerability at least seven times per block. A attack over a longer time-frame would be detectable not only by ViaBTC but also on e.g., fork.observer as ViaBTC would have an abnormal stale block rate. An attacker could also be more careful and exploit this sporadically over a longer time-frame, which could cause serious financial damage if undetected.

Communication with ViaBTC

ViaBTC offers a bug bounty program (mainly scoped for their viabtc.com website) where they list a Zendesk support email to report vulnerabilities to. I didn’t find a dedicated security contact or way to send an encrypted message. My responsible disclosure included all information I know about the vulnerability, details how I verified the vulnerability against the ViaBTC production pool, how they can verify it against a local setup, a discussion of a potential impact if exploited multiple times, a recommendation on how to fix the vulnerability, and some general notes on running old C code with seemingly no test or fuzz coverage for critical business infrastructure. I also included that it would be nice if they could notify potential other pools using the same software. Additionally, I choose to send the ViaBTC CEO and author of the ViaBTC mining server, Haipo Yang, a short summary of the vulnerability and it’s potential impact.

The next day, ViaBTC confirmed they’ve received the vulnerability. Three days later, ViaBTC assigned it severity level 1 of 3 and awarded a 500 USDT bounty. I followed up asking for a reasoning behind the level 1 classification. They re-classified the vulnerability as level 2 and raised the bug bounty reward to 2000 USDT. They also noted that their risk scoring system and monitoring had detected my verification attempt. Furthermore, they claim, a fix was implemented immediately, even before I reported the vulnerability. If true, then the classification as level 2 under “certain asset losses” makes sense. The vulnerability couldn’t have been exploited over a longer time range causing level 3 “serious asset loss” (i.e., ViaBTC paying their PPS+ miners out of their pockets and their PPLNS miners moving away due ViaBTC’s “bad luck”).

While ViaBTC responded quick, funneling the communication with the “Security Risk Team” through a support agent isn’t ideal. Their responses were short and at first I thought they don’t understand the vulnerability, maybe due to a noticeable language barrier. ViaBTC didn’t provide details on how they fixed the vulnerability (they, obviously, don’t need to) and didn’t respond to my offer to re-test the vulnerability. I choose to re-test nonetheless before publishing this disclosure. Ignoring the rough edges, it was still a positive experience working with them.

Timeline

  • 2024-01-18: vulnerability detected and successfully exploited locally
  • 2024-01-19:
    • Confirmed ViaBTC production mining pool is vulnerable
    • Responsible disclosure of the vulnerability to ViaBTC
    • Disclosure of a vulnerability summary to Haipo Yang, ViaBTC CEO and pool software author
    • Vulnerability was fixed immediately after being detected by ViaBTC’s “risk scoring system” (claimed by ViaBTC)
  • 2024-01-20: ViaBTC confirms the vulnerability has been received
  • 2024-01-24: ViaBTC classifies vulnerability as level 2 and awards a 2000 USDT bug bounty
  • 2024-02-06: I re-tested that the vulnerability is indeed fixed
  • 2024-03-20: Public disclosure

Notes

To my knowledge, only ViaBTC was affected. I hope ViaBTC informed other known users of their software about the vulnerability. I contacted a few people in the mining pool community who asked around if anyone is using the ViaBTC mining server, however it doesn’t seem there’s much usage besides ViaBTC. As far as I can tell, all bitpeer connections I receive are from ViaBTC. However, someone might be using the GitHub version to host a mining pool for another coin. I opened an issue in the repository to make potential users aware.

I can’t know for sure if this hasn’t been expoited in the wild, but I don’t have any reason to assume it was before I validated the vulnerability against the ViaBTC production mining pool. If ViaBTC’s “risk scoring system” really detected my attempt, it would likely have detected other attacks too.

Reflection

I think, it was the right decision to test if ViaBTC’s production pool is vulnerable. The claimed internal alarms made sure they took this vulnerability serious and fixed it before I could report it. Additionally, it removed the necessity for ViaBTC to verify a theoretical vulnerability in a test environment – they had confirmation that it works. Still, I could have been more careful when exploiting the vulnerability by choosing the current block to mine an empty block on. This would have cost them the transaction fees, but not the whole block reward. Yet, this might also have not been alerted by their “risk scoring system”.

Running the C code, as uploaded on GitHub, as a production mining server seems scary to me. As far as I can tell, there is no unit testing (there seems to be a few years of in-the-wild-usage-testing though…), no fuzz-testing, error handling seems to be done by returning a failed line number, etc. I personally wouldn’t run this in production. Especially not with 12% of Bitcoin’s hashrate. I suspect the vulnerability existed since Haipo originally wrote the ViaBTC mining server in 2016. While I briefly checked that there weren’t any scary buffer overflows in the code that accepts P2P messages before publishing this disclosure, I decided not to spent more time digging deeper. Usually, disclosing a vulnerability ends up bringing in more eyes on the code. In this case, it wouldn’t surprise me if there are more vulnerabilities to be found.

This once again confirms my impression that some mining pools aren’t as technologically advanced as one might think. Especially older pools might still be stuck with a bit of technical dept. However, ViaBTC seems to have adequate an alerting and monitoring setup.

Credits

Will Clark reminded me about the weird P2P network behavior of what we found out to be ViaBTC bitpeer P2P clients. Furthermore, I found the discussion with Will about the vulnerability and how to best handle it helpful. The MIT DCI provides me with servers I use to run Bitcoin Core nodes alongside some monitoring tools. Having access to multiple nodes was helpful in collecting the initial data about the bitpeer clients. My day-to-day work of, for example, monitoring Bitcoin network anomalies, was funded by Sprial and Human Rights Foundation when I found the vulnerability and is now funded by OpenSats while writing this disclosure. Without their support, I wouldn’t be able to work on Bitcoin full-time. Consider donating to them if you want to support my and others work on Bitcoin.


  1. However, who knows who is still using this… ↩︎

  2. A previous version of this post clailmed that the block would have been valid but stale. This was incorrect. AJ made me aware of this in https://twitter.com/ajtowns/status/1770780046707302514↩︎

All text and images in this work are licensed under a Creative Commons Attribution-ShareAlike 4.0 International License Creative Commons License

Next

Image for Update on LinkingLion: Reduced activity and a statement by LionLink Networks

March 28, 2024

Update on LinkingLion: Reduced activity and a statement by LionLink Networks

This is an update on the LinkingLion entity, presumably linking Bitcoin transactions to IP addresses, I published about a year ago. Yesterday, LionLink Networks AS issued a statement on their non-affiliation with the LinkingLion entity and on the same day, LinkingLion activity significantly dropped.

Previous

Image for 2022 Review and 2023 Outlook

January 17, 2023

2022 Review and 2023 Outlook

In this post, I revisit my plans and projects for 2022, and give an outlook for 2023. I plan to continue my current Bitcoin network monitoring efforts in 2023. I briefly touch on potentially tight open-source developer funding in 2023.