Repository navigation
Commit 2bdbaaf
Merge commit from fork
* fix: prevent script close tag from being swallowed by a single match
`SCRIPT_CLOSE_REGEXP` matched `<\/script[^>]*>`, whose wildcard could run
from one `</script` to the next `>` anywhere in the function source. When a
second, complete `</script>` fell inside that span, the whole thing collapsed
into one match, and since the plain-code branch neutralizes only the leading
`<`, the swallowed tag was re-emitted verbatim.
A function body reaches that shape whenever `</script` appears in code
position -- `x</script=+/` parses as `x < /script=+/`, a comparison against a
regex literal -- followed by a `</script>` in a later string. The serialized
output then carries a live `</script>`, which terminates the script element
when embedded the way the README documents, so the rest of the payload is
parsed as HTML. Confirmed in headless Chromium: the injected `onerror` runs.
This regressed in v7.1.0. v7.0.7 used the same wildcard but escaped the
entire match, so nothing survived.
Excluding `<` from the character class fixes it: a match can no longer reach
past a second `<`, so every `</script` in the source either starts its own
match or is followed by a non-delimiter -- and the HTML tokenizer only ends
the tag name on TAB, LF, FF, CR, SPACE, `/` or `>`, emitting anything else as
text.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: reject spoofed function toString() and make native-code check stateless
Two defects in `serializeFunc`, found while investigating the script close
tag escaping.
`fn.toString()` was trusted to return a string. It is attacker-controlled in
the same way `URL.prototype.toString` and `RegExp.prototype.source` were
before they were hardened. Returning an object with its own `replace()` is
enough to defeat the escaping outright, because `escapeFunctionBody()` is
built entirely from `str.replace(...)` calls -- the object's `replace` simply
returns itself, and the payload reaches the output untouched:
var f = function () {};
f.toString = () => ({
replace: function () { return this; },
toString: () => 'function(){}</script><img src=x onerror=alert(1)>'
});
serialize({ f: f });
// {"f":function(){}</script><img src=x onerror=alert(1)>}
A primitive string from a spoofed `toString()` was already safe; only the
non-string case bypasses escaping. Rejecting it matches how the URL and
RegExp spoofing cases are already handled.
Separately, `IS_NATIVE_CODE_REGEXP` carried a `/g` flag. `.test()` on a
global regexp advances `lastIndex`, so after one rejection the next call
began its scan past the `[native code]` match and returned false, letting
every other native function through:
try { serialize(Math.max); } catch (e) {} // correctly throws
serialize(Math.min); // 'function min() { [native code] }'
That output is a syntax error rather than an injection, so the impact is
limited to an unreliable guard, but the flag serves no purpose here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>1 parent 8c8caa7 commit 2bdbaaf
2 files changed
Lines changed: 115 additions & 10 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
15 | 18 | | |
16 | 19 | | |
17 | 20 | | |
18 | 21 | | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
28 | 42 | | |
29 | 43 | | |
30 | 44 | | |
| |||
206 | 220 | | |
207 | 221 | | |
208 | 222 | | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
209 | 231 | | |
210 | 232 | | |
211 | 233 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
121 | 121 | | |
122 | 122 | | |
123 | 123 | | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
124 | 163 | | |
125 | 164 | | |
126 | 165 | | |
| |||
604 | 643 | | |
605 | 644 | | |
606 | 645 | | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
607 | 690 | | |
608 | 691 | | |
609 | 692 | | |
| |||
0 commit comments