diff --git a/index.js b/index.js index d3fea32..f3db681 100644 --- a/index.js +++ b/index.js @@ -259,7 +259,11 @@ module.exports = function serialize(obj, options) { if (type === 'R') { // Sanitize flags to prevent code injection (only allow valid RegExp flag characters) var flags = String(regexps[valueIndex].flags).replace(/[^gimsuydv]/g, ''); - return "new RegExp(" + serialize(regexps[valueIndex].source) + ", \"" + flags + "\")"; + var regexpSource = regexps[valueIndex].source; + if (typeof regexpSource !== 'string') { + throw new TypeError('RegExp.source must be a string'); + } + return "new RegExp(" + serialize(regexpSource) + ", \"" + flags + "\")"; } if (type === 'M') { diff --git a/test/unit/serialize.js b/test/unit/serialize.js index b4eea83..2fc5709 100644 --- a/test/unit/serialize.js +++ b/test/unit/serialize.js @@ -316,6 +316,41 @@ describe('serialize( obj )', function () { strictEqual(serialize(re), 'new RegExp("[\\u003C\\\\\\u002Fscript\\u003E\\u003Cscript\\u003Ealert(\'xss\')\\\\\\u002F\\\\\\u002F]", "")'); }); + it('should throw when serializing a spoofed RegExp with non-string source', function () { + var fakeRe = Object.create(RegExp.prototype); + Object.defineProperty(fakeRe, 'source', { + enumerable: true, + value: { + toString: function () { + global.__RE_SOURCE_EXECUTED__ = 'pwned-regexp'; + return 'abc'; + } + } + }); + Object.defineProperty(fakeRe, 'flags', { enumerable: true, value: '' }); + throws(function () { serialize({ re: fakeRe }); }, TypeError); + strictEqual(global.__RE_SOURCE_EXECUTED__, undefined); + }); + + it('should throw when serializing a spoofed RegExp with non-string source via getter', function () { + var fakeRegex = Object.create(RegExp.prototype); + Object.defineProperty(fakeRegex, 'source', { + get: function () { + return { + toString: function () { + global.__REGEXP_SOURCE_GETTER_EXECUTED__ = 'pwned'; + return 'x'; + } + }; + } + }); + Object.defineProperty(fakeRegex, 'flags', { + get: function () { return 'g'; } + }); + throws(function () { serialize({ re: fakeRegex }); }, TypeError); + strictEqual(global.__REGEXP_SOURCE_GETTER_EXECUTED__, undefined); + }); + it('should sanitize RegExp.flags to prevent code injection', function () { // Object that passes instanceof RegExp with attacker-controlled .flags var fakeRegex = Object.create(RegExp.prototype);