Skip to content

Commit 2e609d0

Browse files
authored
fix(CVE-2020-7660): fix for RegExp.flags and Date.prototype.toISOString (#207)
* fix(CVE-2020-7660): fix for RegExp.flags and Date.prototype.toISOString * fix: add v
1 parent 42b7cdb commit 2e609d0

File tree

2 files changed

+36
-2
lines changed

2 files changed

+36
-2
lines changed

‎index.js‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,11 +248,18 @@ module.exports = function serialize(obj, options) {
248248
}
249249

250250
if (type === 'D') {
251-
return "new Date(\"" + dates[valueIndex].toISOString() + "\")";
251+
// Validate ISO string format to prevent code injection via spoofed toISOString()
252+
var isoStr = String(dates[valueIndex].toISOString());
253+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/.test(isoStr)) {
254+
throw new TypeError('Invalid Date ISO string');
255+
}
256+
return "new Date(\"" + isoStr + "\")";
252257
}
253258

254259
if (type === 'R') {
255-
return "new RegExp(" + serialize(regexps[valueIndex].source) + ", \"" + regexps[valueIndex].flags + "\")";
260+
// Sanitize flags to prevent code injection (only allow valid RegExp flag characters)
261+
var flags = String(regexps[valueIndex].flags).replace(/[^gimsuydv]/g, '');
262+
return "new RegExp(" + serialize(regexps[valueIndex].source) + ", \"" + flags + "\")";
256263
}
257264

258265
if (type === 'M') {

‎test/unit/serialize.js‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,23 @@ describe('serialize( obj )', function () {
315315
strictEqual(typeof serialize(re), 'string');
316316
strictEqual(serialize(re), 'new RegExp("[\\u003C\\\\\\u002Fscript\\u003E\\u003Cscript\\u003Ealert(\'xss\')\\\\\\u002F\\\\\\u002F]", "")');
317317
});
318+
319+
it('should sanitize RegExp.flags to prevent code injection', function () {
320+
// Object that passes instanceof RegExp with attacker-controlled .flags
321+
var fakeRegex = Object.create(RegExp.prototype);
322+
Object.defineProperty(fakeRegex, 'source', { get: function () { return 'x'; } });
323+
Object.defineProperty(fakeRegex, 'flags', {
324+
get: function () { return '"+(global.__INJECTED_FLAGS="pwned")+"'; }
325+
});
326+
fakeRegex.toJSON = function () { return '@placeholder'; };
327+
var output = serialize({ re: fakeRegex });
328+
// Malicious flags must be stripped; only valid flag chars allowed
329+
strictEqual(output.includes('__INJECTED_FLAGS'), false);
330+
strictEqual(output.includes('pwned'), false);
331+
var obj = eval('obj = ' + output);
332+
strictEqual(global.__INJECTED_FLAGS, undefined);
333+
delete global.__INJECTED_FLAGS;
334+
});
318335
});
319336

320337
describe('dates', function () {
@@ -345,6 +362,16 @@ describe('serialize( obj )', function () {
345362
strictEqual(typeof serialize({t: [d]}), 'string');
346363
strictEqual(serialize({t: [d]}), '{"t":[{"foo":new Date("2016-04-28T22:02:17.156Z")}]}');
347364
});
365+
366+
it('should reject invalid Date ISO string to prevent code injection', function () {
367+
var fakeDate = Object.create(Date.prototype);
368+
fakeDate.toISOString = function () { return '"+(global.__INJECTED_DATE="pwned")+"'; };
369+
fakeDate.toJSON = function () { return '2024-01-01'; };
370+
throws(function () {
371+
serialize({ d: fakeDate });
372+
}, TypeError);
373+
strictEqual(global.__INJECTED_DATE, undefined);
374+
});
348375
});
349376

350377
describe('maps', function () {

0 commit comments

Comments
 (0)