fix: escape ancestor closing tags inside escapable raw text elements (<textarea>, <title>) - #43
Open
hsuan0223x wants to merge 1 commit into
Open
Conversation
The fallbackRawContentTags() hardening covers HTML's raw text elements (<iframe>, <noembed>, <noscript>, <noframes>) but not the escapable raw text elements, <textarea> and <title>. Those are terminated the same way -- only by their own closing tag -- so anything the serializer emits verbatim underneath one of them can close the element early and turn the rest of the payload into live markup. Eight variants produce an executing <img onerror> after a normal reparse: a comment inside <textarea> or <title>, and a nested <style>, <script> or <xmp> inside either. <textarea> and <title> do not belong in hasRawContentFallback: that object's own comment says the text in those elements is inert for browser parsing, which is exactly what is not true of RCDATA -- their text IS escaped for character references. So this adds a separate hasEscapableRawContent set, named after the spec, and has the ancestor walk consult both. The text-node path at NodeUtils.js:322-328 and the element path at :300 are unaffected: both are gated on hasRawContent, and <textarea>/<title> text must keep going through escape(). Adds three cases to test/xss.js following the existing fallbackRawText* structure and using the same puppeteer alertFired() oracle. Each fails on the unpatched tree.
This comment was marked as outdated.
This comment was marked as outdated.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The August hardening of
fallbackRawContentTags()covers the HTML raw text elements(
<iframe>,<noembed>,<noscript>,<noframes>), but not the escapable raw text elements —<textarea>and<title>.Those two are terminated the same way: only by their own closing tag. So anything the serializer
emits verbatim underneath one of them — comment data, processing-instruction data, or the
serialization of a nested raw-text element — can close the element early and turn the remainder of
the payload into live markup.
At
7df6545, eight variants of this produce an executing<img onerror>after a normal HTMLreparse:
<textarea><textarea><!--</textarea><img src=x onerror=alert(1)>--></textarea><title><title><!--</title><img src=x onerror=alert(1)>--></title><style>/<script>/<xmp><textarea><textarea><style></textarea><img src=x onerror=alert(1)></style></textarea><style>/<script>/<xmp><title><title><style></title><img src=x onerror=alert(1)></style></title>Processing instructions happen to survive today, because
escapeProcessingInstructionContent()rewrites the
>and</textarea&is not a valid end tag — but that is incidental rather thanintended, so the tests below cover it as well.
The change
fallbackRawContentTags()already collects the ancestor tags whose closing sequence must be escapedout of nested verbatim data; it just never sees
<textarea>or<title>, because they are not inhasRawContentFallback.They do not belong in that object either — its own comment says the text in those elements "is inert
for browser parsing", which is exactly what is not true of RCDATA:
<textarea>and<title>textis escaped for character references. Overloading the set would make the two families
indistinguishable for any future change that reads it for its documented meaning.
So this adds a separate
hasEscapableRawContentset, named after the spec(escapable raw text elements),
and has the ancestor walk consult both:
That is the whole behavioural change.
hasEscapableRawContentis deliberately not consulted bythe text-node path at
NodeUtils.js:322-328or the element path at:300: both are gated onhasRawContent, and<textarea>/<title>text must keep going throughescape()as it does today.Tests
Three new cases in
test/xss.js, following the existingfallbackRawText*structure and using thesame puppeteer
alertFired()oracle:escapableRawTextCommentNodeEscapesAncestorClosingTag— comment as a direct child and as adescendant, for both tags
escapableRawTextProcessingInstructionEscapesAncestorClosingTagescapableRawTextNestedRawTextElementsEscapeAncestorClosingTagEach new test fails on the unpatched tree, e.g.:
Provenance
Reported to the Google VRP first (issue 555531833). It was closed on 2026-09-04 as not meeting their
internal escalation threshold, with "Please feel free to publicly disclose this issue on GitHub as a
public issue." — hence this PR.
The same serializer obligation has been accepted twice before, in GHSA-j3r3-mxqp-r2p4 (PI data not
escaping an enclosing text-only element's closing tag) and GHSA-v3p8-whq6-r5jg (ancestor walk
terminating early at a
DocumentFragmentboundary). Both corrected how the walk traverses; thiscorrects what it recognises.
angular/angularpins this repo at7df65450b833…inpackage.json, so the path reaches AngularSSR output via
platform-server.