Network not being able to confirm new transactions (total network shutdown)
Direct loss of funds
Description
Bypass Certificate Signing Validation
Impact
Bypass stake certificate validation, allowing for non-staking nodes and network take-over
Bypass nodes removal validation, allowing to remove nodes from the network
Root Cause
The function validateClosestActiveNodeSignatures counts repeated signatures as different signatures, allowing for 1 valid signature to be counted as minRequired. In other words - signatures are counted, instead of signers.
Deep Dive
The functions validateClosestActiveNodeSignatures and validateActiveNodeSignatures receive a parameter minRequired that specify what is the minimal number of nodes need to sign the appData to make it valid.
https://github.com/shardeum/shardus-core/blob/4d75f797a9d67af7a94dec8860220c4e0f9ade3c/src/shardus/index.ts#L1746 It does so by looping over the signature list, and checking if the signature is valid. If it is, the counter is incremented.
https://github.com/shardeum/shardus-core/blob/4d75f797a9d67af7a94dec8860220c4e0f9ade3c/src/shardus/index.ts#L1763 If the amount is more than the min required, true is returned
Remove the public key from closestNodesByPubKey after counting it.
Flow
Malicious node generates a fake JoinRequest with a fake StakingCertificate
It brute-forces StakingCertificate fields to make sure its one of the closest nodes to the hash of the staking certificates. This is easy, as only 1 node is needed to be close.
It creates the full JoinRequest, with multiple copies of its signature, instead of signatures from many other nodes.
It calls gossip-join-request
Other nodes receive the join request, and validate it using validateClosestActiveNodeSignatures.
The validation bypasses, as they count the number of signatures and not the number of signers.
The new node joins the network without staking.
Severity
This allows to take over the network (by kicking nodes / adding nodes) and so it critical.
Proof of concept
POC
Set-up
Clone shardeum (dev branch)
Clone json-rpc-server (dev branch)
Clone simple-network-test (dev branch)
Run npm i inside all three directories
Install shardus according to the readme in shardeum:
Apply the debug-10-nodes.patch with a 5 nodes modification:
diff --git a/src/config/index.ts b/src/config/index.ts
index 245e749..7549557 100644
--- a/src/config/index.ts
+++ b/src/config/index.ts
@@ -132,8 +132,8 @@ config = merge(config, {
p2p: {
cycleDuration: 60,
minNodesToAllowTxs: 1, // to allow single node networks
- baselineNodes: process.env.baselineNodes ? parseInt(process.env.baselineNodes) : 300, // config used for baseline for entering recovery, restore, and safety. Should be equivalient to minNodes on network startup
- minNodes: process.env.minNodes ? parseInt(process.env.minNodes) : 300,
+ baselineNodes: process.env.baselineNodes ? parseInt(process.env.baselineNodes) : 5, // config used for baseline for entering recovery, restore, and safety. Should be equivalient to minNodes on network startup
+ minNodes: process.env.minNodes ? parseInt(process.env.minNodes) : 5,
maxNodes: process.env.maxNodes ? parseInt(process.env.maxNodes) : 1100,
maxJoinedPerCycle: 10,
maxSyncingPerCycle: 10,
@@ -146,7 +146,7 @@ config = merge(config, {
amountToShrink: 5,
maxDesiredMultiplier: 1.2,
maxScaleReqs: 250, // todo: this will become a variable config but this should work for a 500 node demo
- forceBogonFilteringOn: true,
+ forceBogonFilteringOn: false,
//these are new feature in 1.3.0, we can make them default:true in shardus-core later
// 1.2.3 migration starts
@@ -306,11 +306,11 @@ config = merge(
config,
{
server: {
- mode: 'release', // todo: must set this to "release" for public networks or get security on endpoints. use "debug"
+ mode: 'debug', // todo: must set this to "release" for public networks or get security on endpoints. use "debug"
// for easier debugging
debug: {
- startInFatalsLogMode: true, // true setting good for big aws test with nodes joining under stress.
- startInErrorLogMode: false,
+ startInFatalsLogMode: false, // true setting good for big aws test with nodes joining under stress.
+ startInErrorLogMode: true,
robustQueryDebug: false,
fakeNetworkDelay: 0,
disableSnapshots: true, // do not check in if set to false
Apply the suggested local network changes from the docs:
// Local Testing Adjustments
// src/config/index.ts
cycleDuration: 30,
// Generate new block every 3s
// src/shardeum/shardeumFlags.ts
blockProductionRate: 3,
Prepare the shardeum project by running
npm run prepare
inside the shardeum directory.
Start a local network by running
shardus start 5
inside the shardeum directory.
Run a local json-rpc-server by running
npm run start
at the json-rpc-server directory.
Wait for the network to be ready, by looking at the output from the json-rpc-server. We need Current number of good nodes to be 5.
Apply the patch for package.json inside simple-network-test
Update nodeKeyPair in the POC to contain the private and public keys of one of the nodes in the network
Run the POC by calling
node poc.js
Inside simple-network-test.
If the join fails because the cycle is post Q1, wait a few seconds and repeat, in a loop, until submitting in the first quarter of the next cycle.
All nodes should have validateJoinRequest success!!! In their outpus
POC Limitations
As you can see, signatures can be re-used.
It is still required that the malicious node would be one of the 7 closest nodes of the staking certificate hash. This is easily done by brute-force, as only one malicious node need to be in the 7 closest from the network, which is very easily done with the 130K nodes currently on the network.