Skip to content

Commit 15ee698

Browse files
committed
Sanitize option names.
This prevents injection of arbitrary code if the server is already vulnerable to prototype poisoning. This resolves #451. I deliberately opted to not support complex Unicode identifiers even though they're valid JS identifiers. They're complex to validate and users probably shouldn't even try to be that creative.
1 parent c120527 commit 15ee698

File tree

2 files changed

+36
-0
lines changed

2 files changed

+36
-0
lines changed

lib/ejs.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ var _OPTS_PASSABLE_WITH_DATA = ['delimiter', 'scope', 'context', 'debug', 'compi
6464
// so we make an exception for `renderFile`
6565
var _OPTS_PASSABLE_WITH_DATA_EXPRESS = _OPTS_PASSABLE_WITH_DATA.concat('cache');
6666
var _BOM = /^\uFEFF/;
67+
var _JS_IDENTIFIER = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
6768

6869
/**
6970
* EJS template function cache. This can be a LRU object from lru-cache NPM
@@ -587,12 +588,21 @@ Template.prototype = {
587588
' var __output = "";\n' +
588589
' function __append(s) { if (s !== undefined && s !== null) __output += s }\n';
589590
if (opts.outputFunctionName) {
591+
if (!_JS_IDENTIFIER.test(opts.outputFunctionName)) {
592+
throw new Error('outputFunctionName is not a valid JS identifier.');
593+
}
590594
prepended += ' var ' + opts.outputFunctionName + ' = __append;' + '\n';
591595
}
596+
if (opts.localsName && !_JS_IDENTIFIER.test(opts.localsName)) {
597+
throw new Error('localsName is not a valid JS identifier.');
598+
}
592599
if (opts.destructuredLocals && opts.destructuredLocals.length) {
593600
var destructuring = ' var __locals = (' + opts.localsName + ' || {}),\n';
594601
for (var i = 0; i < opts.destructuredLocals.length; i++) {
595602
var name = opts.destructuredLocals[i];
603+
if (!_JS_IDENTIFIER.test(name)) {
604+
throw new Error(`destructuredLocals[${i}] is not a valid JS identifier.`);
605+
}
596606
if (i > 0) {
597607
destructuring += ',\n ';
598608
}

test/ejs.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,3 +1178,29 @@ suite('meta information', function () {
11781178
assert.strictEqual(ejs.name, 'ejs');
11791179
});
11801180
});
1181+
1182+
suite('identifier validation', function () {
1183+
test('invalid outputFunctionName', function() {
1184+
assert.throws(function() {
1185+
ejs.compile('<p>yay</p>', {outputFunctionName: 'x;console.log(1);x'});
1186+
}, /outputFunctionName is not a valid JS identifier/)
1187+
});
1188+
1189+
test('invalid localsName', function() {
1190+
var locals = Object.create(null);
1191+
assert.throws(function() {
1192+
ejs.compile('<p>yay</p>', {
1193+
localsName: 'function(){console.log(1);return locals;}()'});
1194+
}, /localsName is not a valid JS identifier/)
1195+
});
1196+
1197+
test('invalid destructuredLocals', function() {
1198+
var locals = {};
1199+
assert.throws(function() {
1200+
ejs.compile('<p>yay</p>', {
1201+
destructuredLocals: [
1202+
'console.log(1); //'
1203+
]});
1204+
}, /destructuredLocals\[0\] is not a valid JS identifier/)
1205+
});
1206+
});

0 commit comments

Comments
 (0)