Back in March, security researchers reported a critical command injection vulnerability in Parse Server, an open-source backend for Node.js environments.
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.
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, 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.
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.
Object injection vulnerabilities have been identified in other programming languages like Java, PHP, and .NET.
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.
“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.” ®