Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ var UNSAFE_CHARS_REGEXP = /[<>\/\u2028\u2029]/g;
// stays in script data, so only these need neutralizing. The bare-prefix form
// matters because the matching `>` could be supplied by a different
// serialized value later in the output, so escaping stops there without
// waiting for a closing `>`. A trailing backslash is not a delimiter here
// waiting for a closing `>`. For the same reason the bare form also matches
// at the very end of the string: each function body is escaped on its own, so
// a body ending in `</script` can't see the delimiter (e.g. the newline added
// by the `space` option) that the surrounding output supplies next
// (PSECBUGS-118415). A trailing backslash is not a delimiter here
// (that's a JS-level concern, not an HTML one), so this doesn't affect tagged
// template literals like `String.raw`.
//
Expand All @@ -38,7 +42,7 @@ var UNSAFE_CHARS_REGEXP = /[<>\/\u2028\u2029]/g;
// longer contain a second `<`, every `</script` in the source is either the
// start of its own match or is followed by a non-delimiter — and the latter
// the tokenizer never treats as an end tag.
var SCRIPT_CLOSE_REGEXP = /<\/script[^<>]*>|<\/script(?=[\t\n\f\r \/>])/gi;
var SCRIPT_CLOSE_REGEXP = /<\/script[^<>]*>|<\/script(?=$|[\t\n\f\r \/>])/gi;

var RESERVED_SYMBOLS = ['*', 'async'];

Expand Down
24 changes: 24 additions & 0 deletions test/unit/serialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,30 @@ describe('serialize( obj )', function () {
strictEqual(serialized.includes('\\u003C\\u002Fscript\\u003E'), true);
});

it('should escape a script close tag at the very end of a function body (PSECBUGS-118415)', function () {
// Each body is escaped on its own, so one ending in a bare
// `</script` can't see the newline the `space` option emits right
// after it, and a later value can then supply the closing `>`.
var f = function () {};
f.toString = function () { return 'function f(){ return 1 }//</script'; };
var g = function () {};
g.toString = function () { return 'function g(x){ return x } > <img src=x onerror=alert(1)>'; };
var serialized = serialize({ a: { f: f }, g: g }, { space: 2 });

strictEqual(CLOSES_SCRIPT.test(serialized), false);
// Inside the line comment, so it unicode-escapes.
strictEqual(serialized.includes('//\\u003C\\u002Fscript\n'), true);
});

it('should neutralize a script close tag ending a function body in code position', function () {
var fn = function () {};
fn.toString = function () { return 'function f(x){ return x }</script'; };
var serialized = serialize([fn], { space: 2 });

strictEqual(CLOSES_SCRIPT.test(serialized), false);
strictEqual(serialized.includes('}< /script\n'), true);
});

it('should escape every script close tag when several appear in one function', function () {
var src = "function f(x){ return x</script=+/ + '</script>' + '</script>' + '</script >' }";
var fn = new Function('return ' + src)();
Expand Down
Loading