33745 - [BC - Critical] A math quirk in Javascript allows anyone to tak...
A math quirk in Javascript allows anyone to take down any validator or the full network with an HTTP GET request.
Submitted on Jul 28th 2024 at 11:09:12 UTC by @infosec_us_team for Boost | Shardeum: Core
Report ID: #33745
Report type: Blockchain/DLT
Report severity: Critical
Target: https://github.com/shardeum/shardeum/tree/dev
Impacts:
Network not being able to confirm new transactions (total network shutdown)
Description
Brief/Intro
In this report, we demonstrate how to abuse a Javascript quirk related to "loss of precision" when doing math with large numbers, to send an HTTP GET request that can take down a single victim node or the entire network.
Description
Javascript precision loss with large numbers
Understanding the existence of the quirks we will abuse is crucial to understanding at its core why the code is exploited, and how to correctly patch the bug.
Please, to follow along, we strongly recommend going to a Javascript Playground, or opening the browser console, and experimenting with the concepts we'll explain.
1- Auto-rounding
Let's start by checking the output to the following Javascript code:
Instead of "a_number: 9999999999999999"
as you may expect, due to how Floating-Point Arithmetic works in Javascript the number is rounded up as soon as it is assigned to a variable.
The same happens if we use parseInt(...)
and give it as a string:
2- Rounding down in arithmetic operations
If we add 1 to this value (10000000000000000), the result rounds down by 1, leaving the operation unchanged:
It doesn't matter how many times we add 1 unit to 10000000000000000, the result is always the same:
These behaviors are not a JavaScript "feature" and have been around for quite a while. Instead of trying to explain them here, we'll just leave a link to a great website dedicated exclusively to explaining how floating-point math works: https://floating-point-gui.de/
Now that you have an idea of what type of quirks we exploit in this report, let's dive into the Shardeum code.
Vulnerable code
The vulnerable entry point is the HTTP GET route eth_getBlockHashes
. Here's as a reference:
https://github.com/shardeum/shardeum/blob/dev/src/index.ts#L1275C1-L1295C5
Let's debug it step-by-step.
Step 1- It asks for 2 values, fromBlock and toBlock.
In our exploit we will visit the following URL: http://SERVER_IP
:VICTIM_PORT
/eth_getBlockHashes?fromBlock=0&toBlock=-9999999999999999 therefore the values for fromBlock
and toBlock
will be 0
and -9999999999999999
.
Step 2- If the type of value given in toBlock is a string - which it is being interpreted as one - then the value of fromBlock
is replaced with the output of parseInt(toBlock)
.
As we saw in the section above, you may expect the output to be -9999999999999999, but due to how Floating-Point Arithmetic works in Javascript is -10000000000000000
Step 3- We start looping from an index i
which starts with the same value as fromBlock
(-10000000000000000), we add 1 unit to i
every time the code is ran, and we do it until i
is bigger than the value of toBlock
(-9999999999999999)
Due to how Floating-Point Arithmetic works in Javascript, adding 1 + -10000000000000000
results in -10000000000000000
. The value does not increase.
As a result, the for loop runs forever, it never stops.
Impact Details
The victim can be a single node or all nodes in the blockchain - it doesn't matter if they are active or not - bricking the entire network.
Anyone can exploit it. The attacker doesn't have to be a validator in the network.
The victim node will run code in a loop forever, aggressively consuming computing resources.
All other endpoints (HTTP and Gossips) of the victim node become unresponsive. Visiting or requesting any of them will timeout.
The rest of the nodes start to timeout their requests to the victim node and report him as lost, which may lead to a slashing event for becoming unresponsive without gossiping anything to the rest of the network.
The victim node is removed from the network.
Proof of Concept
Step 1- Start the Shardeum network locally.
Step 2- Wait until cycle counter 15, where all nodes are active.
Step 3- Visit the following link: http://SERVER_IP
:VICTIM_PORT
/eth_getBlockHashes?fromBlock=0&toBlock=-9999999999999999
Replace
SERVER_IP
with the IP of the server andVICTIM_PORT
with the PORT where the victim node is running.
You have now bricked the node.
Visit the network monitor at
http://SERVER_IP:3000
and you will see a red dot over the victim node, instead of a green one.Some nodes will try to interact with the victim and all their messages will timeout.
Below is an example of the output from a healthy node running in port 9002, trying to interact with the victim node to compare certs:
This output is from a log file located at
./instances/shardus-instance-9002/logs/main.log
- the victim node was running at port 9005 and the healthy one trying to contact him was at port 9002.
Repeat Step 3 as many times as desired to take down as many validators as you wish.
Last updated