Skip to content

Commit 2a48705

Browse files
committed
Fix prototype pollution in Configuration.set/unset via __proto__ key traversal #6089
1 parent b6ef0ad commit 2a48705

4 files changed

Lines changed: 74 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/node_modules
2+
.claude
23
claude
34
CLAUDE.md
45
*.log

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- Expand Docker parallel test runner to cover all internalized module tests
1919
- Drop auto source map file detection in Common.prepareAppConf
2020
- Fix HttpInterface env stripping never executing (WEB_STRIP_ENV_VARS) #6089
21+
- Fix prototype pollution in Configuration.set/unset via __proto__ key traversal #6089
2122

2223
## 6.0.14
2324

lib/Configuration.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ function splitKey(key) {
2020
else if (key.indexOf(':') > -1)
2121
values = key.match(/(?:[^:"]+|"[^"]*")+/g).map(function(dt) { return dt.replace(/"/g, '') });
2222

23+
var dangerousKeys = ['__proto__', 'constructor', 'prototype'];
24+
if (values.some(function(v) { return dangerousKeys.indexOf(v) !== -1; })) {
25+
return [];
26+
}
27+
2328
return values;
2429
}
2530

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
2+
process.chdir(__dirname)
3+
4+
var should = require('should')
5+
var PM2 = require('../..')
6+
var Configuration = require('../../lib/Configuration.js')
7+
8+
describe('Issue #6089 - Configuration prototype pollution', function () {
9+
this.timeout(30000)
10+
11+
before(function (done) {
12+
PM2.list(done)
13+
})
14+
15+
afterEach(function () {
16+
delete Object.prototype.polluted
17+
delete Object.prototype.rce
18+
})
19+
20+
describe('set (async)', function () {
21+
it('should reject __proto__ key', function (done) {
22+
Configuration.set('__proto__.polluted', 'yes', function (err) {
23+
should(({}).polluted).be.undefined()
24+
done()
25+
})
26+
})
27+
28+
it('should reject constructor.prototype key', function (done) {
29+
Configuration.set('constructor.prototype.polluted', 'yes', function (err) {
30+
should(({}).polluted).be.undefined()
31+
done()
32+
})
33+
})
34+
})
35+
36+
describe('setSync', function () {
37+
it('should reject __proto__ key', function () {
38+
Configuration.setSync('__proto__.polluted', 'yes')
39+
should(({}).polluted).be.undefined()
40+
})
41+
42+
it('should reject prototype key', function () {
43+
Configuration.setSync('constructor.prototype.polluted', 'yes')
44+
should(({}).polluted).be.undefined()
45+
})
46+
})
47+
48+
describe('unset (async)', function () {
49+
it('should reject __proto__ traversal', function (done) {
50+
Object.prototype.rce = 'exists'
51+
Configuration.unset('__proto__.rce', function (err) {
52+
should(({}).rce).eql('exists')
53+
delete Object.prototype.rce
54+
done()
55+
})
56+
})
57+
})
58+
59+
describe('unsetSync', function () {
60+
it('should reject __proto__ traversal', function () {
61+
Object.prototype.rce = 'exists'
62+
Configuration.unsetSync('__proto__.rce')
63+
should(({}).rce).eql('exists')
64+
delete Object.prototype.rce
65+
})
66+
})
67+
})

0 commit comments

Comments
 (0)