Node.js prototype pollution is bad for your app environment

Boffins find common code constructs that may be exploitable to achieve remote code execution


Back in March, security researchers reported a critical command injection vulnerability in Parse Server, an open-source backend for Node.js environments.

Rated 10 out of 10 on the CVSS scale of severity, the remote code execution hole was the result of prototype pollution, a security oversight that can be abused to hijack JavaScript code and the JavaScript-based Node.js runtime.

The boffins who identified the Parse Server flaw – Mikhail Shcherbakov and Musard Balliu, from KTH Royal Institute of Technology, Cristian-Alexandru Staicu, from CISPA Helmholtz Center for Information Security – did so by creating a framework for detecting prototype pollution through a combination of static and dynamic analysis.

They describe their work in a paper titled, "Silent Spring: Prototype Pollution Leads to Remote Code Execution in Node.js," which was distributed this month and apparently has been submitted to next year's USENIX '23. Using their framework, which is built atop GitHub's static analysis framework CodeQL, they said they were able to find 11 universal gadgets – existing code structures – in the code Node.js API that can potentially enable remote code execution.

​​Prototype pollution is one of the most common security vulnerabilities found in JavaScript code

They then applied their approach of 15 popular Node.js applications and identified three instances vulnerable to remote code execution via prototype pollution. One was Parse Server, two others were in the NPM CLI.

"​​Prototype pollution is one of the most common security vulnerabilities found in JavaScript code," said Feross Aboukhadijeh, an open-source developer and founder of security scanning service Socket, in an email to The Register. "The issue is so common in JavaScript, in particular, because of the language’s design which heavily uses 'prototypal inheritance.'"

JavaScript code can use what's called objects, which are collections of properties. Objects inherit properties from other objects through a prototype property.

Prototype pollution, described [PDF] by Olivier Arteau in 2018, can occur when an attacker is able to set properties in a prototype, which causes all child objects to inherit that property.

"The vulnerability is rooted in the permissive nature of the language, which allows the mutation of an important built-in object in the global scope – Object.prototype – called the root prototype," the Silent Spring paper explains. "JavaScript’s prototype-based inheritance enables accessing this important object through the prototype chain."

By providing specific properties names that will be accessed at runtime, an attacker can make vulnerable code alter the root prototype, appending an attacker-controlled property to every child object.

"Whenever JavaScript code attempts to access a property that doesn’t exist, the runtime will look to a 'parent' object for the existence of that property, which is where a polluted prototype has a chance to change the behavior of a program," explained Aboukhadijeh.

Object injection vulnerabilities have been identified in other programming languages like Java, PHP, and .NET.

Shcherbakov describes the Parser Server attack in a bug bounty write-up submitted last December. And the paper he co-authored with Balliu and Staicu details the two NPM CLI attacks.

The 11 universal gadgets in the Node.js API involve polluting properties such as "main" using code like:

Object.prototype.main = "./../../pwned.js"
// trigger call
require("my-package")

In this scenario, "my-package" would not have a main property defined in its package.json file.

"If the main property of the root prototype is polluted, at require time, the value of this property is used for retrieving the code to be executed, instead of the legitimate code of the module," the researchers explain. "The attacker can thus indicate an arbitrary file on the disk to be loaded in the engine."

The authors emphasize that it's not necessarily easy or possible to successfully carry out a prototype pollution attack where suitable gadgets exist in application code. But they urge developers to treat the issue seriously.

They looked at the 10,000 most-depended-upon npm packages and found many contained potentially abusable code: 1,958 have no main entry point for their package.json file, 4,420 use relative paths inside require statements, and 355 directly use command injection.

While these figures represent an upper bound on the actual prevalence of exploitable gadgets – because of other complicating factors – they argue more attention needs to be given to guarding against prototype pollution in the JavaScript ecosystem.

"We emphasize once again how dangerous the identified gadgets are," they say, observing that many applications are likely to meet the preconditions for remote code execution if prototype pollution is possible. "...[C]onsidering the power of these gadgets and their widely-available triggers, prototype pollution should be considered a critical security vulnerability in the current Node.js landscape."

"This paper is interesting because it uses call flow analysis starting at untrusted entry points to determine if an attack payload can actually reach an attack sink," said Aboukhadijeh, referring to the process of passing a polluted property to a security-relevant gadget.

"I was impressed that the authors were able to find three confirmed exploitable cases of prototype pollution, which is a nice change from the usual noise we get from CVE reports about prototype pollution, most of which are not actually exploitable. This research shows that prototype pollution is not just a theoretical risk." ®

Broader topics


Other stories you might like

Biting the hand that feeds IT © 1998–2022