As a Node.js developer and safety researcher, I lately stumbled upon an fascinating safety regression within the Node.js core undertaking associated to prototype air pollution.
This occurred to be discovered whereas I used to be conducting an impartial safety analysis for my Node.js Safe Coding books and but the invention highlights the complicated nature of safety in open-source tasks and the challenges of sustaining constant safety measures throughout a big codebase. Even on the scale of a undertaking like Node.js, regressions can happen, doubtlessly leaving components of the codebase weak to assault.
The Discovery: A Journey Down Prototype Lane
Again in 2018, I opened a Pull Request to deal with a possible prototype air pollution vulnerability within the child_process
module. The PR aimed to repair shallow object checks like if (choices.shell)
, which may very well be vulnerable to prototype air pollution assaults. Nevertheless, the Node.js core group and Technical Steering Committee (TSC) determined to not land the PR on the time on account of issues that such a change would then benefit larger API adjustments in different core modules. As such, an settlement couldn’t be reached to protect in opposition to prototype air pollution within the child_process
module.
Quick ahead to July 2023 with an identical change that did get merged by means of a Pull Request to harden in opposition to prototype air pollution for child_process. This obtained me pondering: has the difficulty been totally resolved, or are there nonetheless lingering vulnerabilities?
Node.js Core Regression of Inconsistent Prototype Hardening
To research, I arrange a easy proof-of-concept to check numerous child_process
capabilities. Right here’s what I discovered:
const { execFile, spawn, spawnSync, execFileSync } = require("child_process");
// Simulate a profitable prototype assault affect:
const a = {};
a.__proto__.shell = true;
console.log("Object.shell value:", Object.shell, "n");
// Check numerous child_process capabilities:
execFile("ls", ["-l && touch /tmp/from-ExecFile"], {
stdio: "inherit",
});
spawn("ls", ["-la && touch /tmp/from-Spawn"], {
stdio: "inherit",
});
execFileSync("ls", ["-l && touch /tmp/from-ExecFileSync"], {
stdio: "inherit",
});
spawnSync("ls", ["-la && touch /tmp/from-SpawnSync"], {
stdio: "inherit",
});
Operating the above code snippet in a Node.js atmosphere yields the next output:
$ node app.js
Object.shell worth: true
[...]
$ ls -alh /tmp/from*
Permissions Measurement Person Date Modified Identify
.rw-r--r-- 0 lirantal 4 Jul 14:14 /tmp/from-ExecFileSync
.rw-r--r-- 0 lirantal 4 Jul 14:14 /tmp/from-Spawn
.rw-r--r-- 0 lirantal 4 Jul 14:14 /tmp/from-SpawnSync
The outcomes are stunning:
execFile()
andspawn()
had been correctly hardened in opposition to prototype air pollution.- Nevertheless,
execFileSync()
,spawnSync()
, andspawn()
(when supplied with anchoices
object) had been nonetheless weak.
This inconsistency signifies that whereas some components of the child_process
module are protected, others stay uncovered to potential prototype air pollution assaults.
The detailed anticipated vs precise outcomes are as follows:
Expectation
- Per the spawn() API documentation,
spawn()
ought to default toshell: false
. Equally,execFile()
follows the identical. - Per the referenced prototype air pollution hardening Pull Request from 2023, the next simulated assault shouldn’t work:
Object.prototype.shell = true; child_process.spawn('ls', ['-l && touch /tmp/new'])
Precise
Object.prototype.shell = true; child_process.execFile('ls', ['-l && touch /tmp/new'])
– ✅ No uncomfortable side effects, hardening works properly.Object.prototype.shell = true; child_process.spawn('ls', ['-l && touch /tmp/new'])
– ✅ No uncomfortable side effects, hardening works properly.Object.prototype.shell = true; child_process.execFile('ls', ['-l && touch /tmp/new'], { stdio: 'inherit'})
– ✅ No uncomfortable side effects, hardening works properly.Object.prototype.shell = true; child_process.spawn('ls', ['-l && touch /tmp/new'], { stdio: 'inherit'})
– ❌ Vulnerability manifests, hardening fails.
The Safety Implications
Now, you is perhaps questioning: “Is this a critical security vulnerability in Node.js?” The reply will not be as simple as you may suppose.
In response to the Node.js Safety Menace Mannequin:
Prototype Air pollution Assaults (CWE-1321) Node.js trusts the inputs offered to it by software code. It’s as much as the appliance to sanitize appropriately. Due to this fact any state of affairs that requires management over consumer enter will not be thought-about a vulnerability.
In different phrases, whereas this regression does introduce a safety danger, it’s not formally labeled as a vulnerability within the Node.js core undertaking. The reasoning behind that is that Node.js expects builders to deal with enter sanitization of their purposes.
What This Means for Node.js Builders
As a Node.js developer, this discovering underscores just a few necessary factors:
- All the time validate and sanitize consumer enter: Don’t rely solely on Node.js core protections. Implement strong enter validation in your purposes.
- Keep up to date: Control Node.js releases and safety advisories. Node.js safety releases are a daily incidence, and it’s important to remain knowledgeable about potential vulnerabilities.
- Perceive the safety mannequin: Familiarize your self with the Node.js Safety Menace Mannequin to raised perceive what protections are (and aren’t) offered by the core undertaking.
Transferring Ahead: Addressing the Regression
Whereas this concern will not be labeled as an official safety vulnerability (and didn’t warrant a CVE), it’s nonetheless a bug that wants addressing.
I’ve opened a Pull Request to the Node.js core undertaking to deal with this inconsistency within the child_process
module. The PR goals to make sure that the remaining weak capabilities within the child_process
module are constantly hardened in opposition to prototype air pollution assaults.
Conclusion
This discovery serves as a reminder that safety is an ongoing course of, even in well-established tasks like Node.js. One other fascinating side right here when it comes to knowledge evaluation is how lengthy this safety regression has been current within the Node.js core undertaking with out anybody pointing it out. It’s a testomony to the complexity of sustaining safety throughout a big codebase and the challenges of guaranteeing constant safety measures.