Skip to content

Commit f5d78e4

Browse files
Seth Bertalottogtcarlos-sage
authored andcommitted
fix(CVE-2020-7660): fix for RegExp.flags and Date.prototype.toISOString (yahoo#207)
* fix(CVE-2020-7660): fix for RegExp.flags and Date.prototype.toISOString * fix: add v
1 parent b71ec23 commit f5d78e4

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
@@ -226,11 +226,18 @@ module.exports = function serialize(obj, options) {
226226
}
227227

228228
if (type === 'D') {
229-
return "new Date(\"" + dates[valueIndex].toISOString() + "\")";
229+
// Validate ISO string format to prevent code injection via spoofed toISOString()
230+
var isoStr = String(dates[valueIndex].toISOString());
231+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/.test(isoStr)) {
232+
throw new TypeError('Invalid Date ISO string');
233+
}
234+
return "new Date(\"" + isoStr + "\")";
230235
}
231236

232237
if (type === 'R') {
233-
return "new RegExp(" + serialize(regexps[valueIndex].source) + ", \"" + regexps[valueIndex].flags + "\")";
238+
// Sanitize flags to prevent code injection (only allow valid RegExp flag characters)
239+
var flags = String(regexps[valueIndex].flags).replace(/[^gimsuydv]/g, '');
240+
return "new RegExp(" + serialize(regexps[valueIndex].source) + ", \"" + flags + "\")";
234241
}
235242

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

test/unit/serialize.js

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

321338
describe('dates', function () {
@@ -342,6 +359,16 @@ describe('serialize( obj )', function () {
342359
expect(serialize(d)).to.be.a('string').equal('{"foo":new Date("2016-04-28T22:02:17.156Z")}');
343360
expect(serialize({t: [d]})).to.be.a('string').equal('{"t":[{"foo":new Date("2016-04-28T22:02:17.156Z")}]}');
344361
});
362+
363+
it('should reject invalid Date ISO string to prevent code injection', function () {
364+
var fakeDate = Object.create(Date.prototype);
365+
fakeDate.toISOString = function () { return '"+(global.__INJECTED_DATE="pwned")+"'; };
366+
fakeDate.toJSON = function () { return '2024-01-01'; };
367+
throws(function () {
368+
serialize({ d: fakeDate });
369+
}, TypeError);
370+
strictEqual(global.__INJECTED_DATE, undefined);
371+
});
345372
});
346373

347374
describe('maps', function () {

0 commit comments

Comments
 (0)