HTML Parser ambiguity with inline svg and nested styles
Categories
(Core :: DOM: HTML Parser, defect)
Tracking
()
People
(Reporter: freddy, Unassigned)
References
(Blocks 1 open bug, )
Details
(Keywords: sec-want)
Attachments
(2 files, 1 obsolete file)
| Reporter | ||
Updated•10 years ago
|
Comment 1•10 years ago
|
||
| Reporter | ||
Comment 2•10 years ago
|
||
Comment 3•10 years ago
|
||
Comment 5•10 years ago
|
||
| Reporter | ||
Comment 6•10 years ago
|
||
Comment 7•10 years ago
|
||
Comment 8•10 years ago
|
||
| Reporter | ||
Comment 9•6 years ago
|
||
(In reply to Robert Longson [:longsonr] from comment #8)
With document.write you're going to create a legacy non-html5 output aren't
you but with innertHTML it will be html5 if the original document is html5
because you're just replacing the children of the existing body.Does the following create a safe DOM?
document.write('<!DOCTYPE><svg><p><style><img src="</style><img src=x
onerror=alert(1)//">')
It doesn't. The doctype doesn't seem to change things.
| Reporter | ||
Comment 11•6 years ago
|
||
This has been unfixed for so long. Do you think you can help with this, Henri?
I think it might cause to website XSS, similar to the other parser problems we've had recently.
Comment 12•6 years ago
|
||
Henri is still occupied by Fission. Alphan will help investigation here first.
| Reporter | ||
Comment 13•6 years ago
|
||
Alphan asked out of bands which issues I was referring to in comment 11.
It was mostly meant for context to judge the severity. I'm not entirely sure how relevant they will be for solving this particular issue. But for the record, I found HTML parsing security issues that we had in 2019 and in 2020 (until today):
Comment 14•6 years ago
|
||
Comment 15•6 years ago
|
||
(In reply to Frederik Braun [:freddy] from comment #0)
(This bug is in so far related to mxss as we appear to see a mutation when
assigning to innerHTML, funnily the mutation prevents the execution of
JavaScript, when in "mxss" it creates an unexpected JS execution)
(In reply to Robert Longson [:longsonr] from comment #5)
Per http://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerhtml0
Note: script elements inserted using innerHTML do not execute when they are
inserted.
When opening test-new.html(attachment 9138928 [details]), you can see both "innerHTML" and "document.write()".
It means FF doesn't prevent the execution of JavaScript when assigning the code into innerHTML.
Currently, I don't know the reason for the different parsing results(svg id 1 and svg id 2) when opening test2.html(attachment 9138928 [details]).
I will keep investigating it.
Compare the results of Chrome and Firefox:
[test2.html]
Firefox: only show "/document.write/"
Chrome: show "/innerHTML/" and "/document.write/"
[test-new.html]
Firefox: show "innerHTML" and "document.write()"
Chrome: show "innerHTML" and "document.write()"
Comment 16•6 years ago
|
||
Summarize current status, the reason why Firefox seems to be safer when setting .innerHTML is we have different DOM trees after parsing.
Hi Freddy,
I would like to know the main purpose of this bug.
Are we focusing on the reason for different parsing results(DOM trees) or security issue here?
From the bug title, I suppose that we are focusing on the reason here.
| Reporter | ||
Comment 17•6 years ago
•
|
||
Yes, Alphan. I agree that the parsed trees are different, which links back to the comment 6:
The DOM tree created for document.write is
svg
p
style
img
(all nested. The img within style isn't parsed as HTML of course)
Using innerHTML, the DOM tree is:
svg
p
style
img
(svg, p and img all as children of the same parent node)
The bug was filed due to a security issue in a popular web security library, called DOMPurify. The library has worked around the issue, but we should still track this as a compliance/interoperability issue with other browsers.
Comment 18•6 years ago
|
||
For innerHTML, we have a specific parser called nsHtml5StringParser.
https://searchfox.org/mozilla-central/rev/7fd1c1c34923ece7ad8c822bee062dd0491d64dc/dom/base/FragmentOrElement.cpp#1959
https://searchfox.org/mozilla-central/rev/7fd1c1c34923ece7ad8c822bee062dd0491d64dc/dom/base/nsContentUtils.cpp#4835
For document.write(), we use normal parser called nsHtml5Parser.
https://searchfox.org/mozilla-central/rev/7fd1c1c34923ece7ad8c822bee062dd0491d64dc/dom/base/Document.cpp#9115
Here are some related bugs about nsHtml5StringParser and nsHtml5Parser
In bug 714777, we move fragment parsing out of nsHtml5Parser and create nsHtml5StringParser.
In bug 959150, we make innerHTML faster by not having to deal with tree ops. Here we introduce a class implements a minimal subclass of nsHtml5DocumentBuilder called nsHtml5OplessBuilder. Therefore, nsHtml5TreeOpExecutor is not used.
For nsHtml5StringParser, we have mBuilder in nsHtml5TreeBuilder but mOpSink is nullptr.
For nsHtml5StringParser, we have mOpSink in nsHtml5TreeBuilder but mBuilder is nullptr.
Normally, we just have corresponding behavior according to what we have(mBuilder or mOpSink).
But there are still some different behaviors in some cases.
Comment 19•6 years ago
|
||
For the symptom of this bug, the biggest difference is what will the treeBuilder do when there is an error in "HtmlStartTagInForeignContext".
https://searchfox.org/mozilla-central/rev/7fd1c1c34923ece7ad8c822bee062dd0491d64dc/parser/html/nsHtml5TreeBuilder.cpp#727
For document.write(), "fragement" is false.
We will popForeign(). But "fragement" is true when setting innHtml.
This causes two different DOM tree layouts as below.
[Use doc.body.innerHTML="..."]
<svg id="1">
= <p>
== <style>
=== <img src="</style><img src=x onerror=alert(/innerHTML/)//">
== </style>
= <p>
</svg>
[Use document.write("...")]
<svg id="2"></svg>
<p>
= <style><img src="</style>
= <img src=x onerror=alert(/innerHTML/)//">
</p>
Comment 20•6 years ago
•
|
||
[Use document.write("...")]
<svg id="2"></svg>
<p>
= <style><img src="</style>
= <img src=x onerror=alert(/innerHTML/)//">
</p>
[Tree build process]
nsHtml5TreeBuilder::createElement (aNamespace = kNameSpaceID_SVG)
nsHtml5TreeBuilder::appendElement
nsHtml5TreeBuilder::elementPushed
nsHtml5TreeBuilder startTag(name=p)
--- call errEndTagDidNotMatchCurrentOpenElement ()
--- fragment=false call popForeign ---------------------------------------------> main difference
nsHtml5TreeBuilder::popForeign
nsHtml5TreeBuilder::markMalformedIfScript
nsHtml5TreeBuilder::elementPopped
nsHtml5TreeBuilder::createElement (p) (aNamespace = kNameSpaceID_XHTML)
nsHtml5TreeBuilder::appendElement
nsHtml5TreeBuilder::elementPushed
nsHtml5TreeBuilder::createElement (style) (aNamespace = kNameSpaceID_XHTML)
nsHtml5TreeBuilder::appendElement
nsHtml5TreeBuilder::elementPushed
nsHtml5TreeBuilder::appendCharacters
nsHtml5TreeBuilder::elementPopped(style)
nsHtml5TreeBuilder::createElement (img)
nsHtml5TreeBuilder::createElement (img) (aNamespace = kNameSpaceID_XHTML)
nsHtml5TreeBuilder::appendElement
nsHtml5TreeBuilder::elementPushed
nsHtml5TreeBuilder::elementPopped(img)
Comment 21•6 years ago
|
||
[Use doc.body.innerHTML="..."]
<svg id="1">
= <p>
== <style>
=== <img src="</style><img src=x onerror=alert(/innerHTML/)//">
== </style>
= <p>
</svg>
Since we don't popForeign, the namespace is always in kNameSpaceID_SVG.
nsHtml5TreeBuilder::createElement(svg)(aNamespace = kNameSpaceID_SVG)
nsHtml5TreeBuilder::appendElement
nsHtml5TreeBuilder::elementPushed
nsHtml5TreeBuilder startTag(name=p)
--- call errHtmlStartTagInForeignContext
nsHtml5TreeBuilder::errHtmlStartTagInForeignContext
--- fragment=true --------------------------------------------> main difference
nsHtml5TreeBuilder::createElement(p)(aNamespace = kNameSpaceID_SVG)
nsHtml5TreeBuilder::appendElement
nsHtml5TreeBuilder::elementPushed
nsHtml5TreeBuilder::createElement(style)(aNamespace = kNameSpaceID_SVG)
nsHtml5TreeBuilder::appendElement
nsHtml5TreeBuilder::elementPushed
nsHtml5TreeBuilder startTag(name=img)
--- call errHtmlStartTagInForeignContext
nsHtml5TreeBuilder::errHtmlStartTagInForeignContext
--- fragment=true
nsHtml5TreeBuilder::createElement(img)(aNamespace = kNameSpaceID_SVG)
nsHtml5TreeBuilder::appendElement
nsHtml5TreeBuilder::elementPushed
call nsHtml5TreeBuilder::popOnEof
nsHtml5TreeBuilder::markMalformedIfScript
nsHtml5TreeBuilder::elementPopped(img)
call nsHtml5TreeBuilder::popOnEof
nsHtml5TreeBuilder::markMalformedIfScript
nsHtml5TreeBuilder::elementPopped(style)
--- call mBuilder->UpdateStyleSheet
call nsHtml5TreeBuilder::popOnEof
nsHtml5TreeBuilder::markMalformedIfScript
nsHtml5TreeBuilder::elementPopped(p)
call nsHtml5TreeBuilder::popOnEof
nsHtml5TreeBuilder::markMalformedIfScript
nsHtml5TreeBuilder::elementPopped(svg)
--- call nsHtml5TreeOperation::SvgLoad()
Comment 22•6 years ago
|
||
From comment 18-21, we know it is our design which makes the difference.
However, I think that it makes sense that we have the same parsing result when doing document.write() and setting document.body.innerHTML.
Hello Henri, what is your opinion?
Comment 23•6 years ago
•
|
||
Thanks for tracing this. This is working as designed: https://html.spec.whatwg.org/#parsing-main-inforeign
In retrospect, it's debatable whether it's a good idea for the rule not to apply in the fragment case, but it would also be kinda weird to break out in the fragment case.
The backstory for why we break out in the non-fragment case is that when Hixie was writing this part of the spec, he ran a query over the Google index and found random Web pages that had incomplete SVG fragments in them. Before HTML had SVG support, these fragments didn't have user-visible effect. However, without the break-out rule, the part of these pages after the incomplete SVG fragment would have become invisible. Therefore, browsers would have had to have taken a Web compat hit in order to ship SVG-in-HTML. This way, there wasn't a Web compat impediment to shipping SVG-in-HTML.
document.write() is consistent with parsing from network.
Marking INVALID in the sense that the allegation of behavior is true, but the behavior is not a bug per spec.
Sites that care about security should parse, then apply an allow-list, and then reserialize. It's unfortunate that "should parse" isn't a single thing but contextual whether the target is fragment parsing or not.
Comment 24•6 years ago
|
||
That however generated a DOM that was missing the offending element. And that only happened on Firefox.
Except it looks like Chrome has a bug: It appears to apply the break-out behavior in the fragment case.
Reopening for assessing if we should change the spec to match Chrome's failure to implement the spec.
Anne, what do you think?
Comment 25•6 years ago
|
||
Since Safari is aligned with Chrome, I would suggest we align and update the HTML Standard.
Comment 26•6 years ago
|
||
It's even on file already: https://github.com/whatwg/html/issues/5117
Comment 27•6 years ago
|
||
The special case in the spec came from: https://www.w3.org/Bugs/Public/show_bug.cgi?id=17924
| Reporter | ||
Comment 28•5 years ago
|
||
The HTML standard update is going to happen in https://github.com/whatwg/html/pull/6399.
I think it's time for us to make the change.
Comment 29•5 years ago
|
||
wpt tests updated in https://github.com/web-platform-tests/wpt/pull/27799 which was just merged.
The 6399 spec change introduced a regression for foster parenting; see https://github.com/whatwg/html/pull/6455
| Reporter | ||
Comment 30•5 years ago
|
||
Henri, with the HTML spec change above, can you take this on?
Comment 31•5 years ago
|
||
(In reply to Frederik Braun [:freddy] from comment #30)
Henri, with the HTML spec change above, can you take this on?
Assigning to self.
Updated•3 years ago
|
Comment 32•1 year ago
|
||
I've just hit this issue when looking into jQuery HTML parsing which currently uses document.implementation.createHTMLDocument( "" ). Three years have passed since the last update, is there any chance it will be picked any time soon?
Comment 33•1 year ago
|
||
Adjusting the assigned state to better communicate that it's OK for someone else to take a look.
Comment 34•1 year ago
|
||
The spec for parsing HTML fragments works by first adding a <html> node to the stack of open elements and then using the context element (e.g. some SVG element) for setting up the start tag. As far as I can tell other browsers follow this approach.
However in the our java-based HTML parser code we instead just use the context element for creating the root node (see this comment, added by bug 886390). This means we can't really pop until reaching a HTML namespace when parsing certain HTML elements in a foreign content. I am not sure what would be more tricky, trying to fix this case specifically or just removing this optimization and following the specification more closely.
Comment 35•1 year ago
|
||
(In reply to Tom Schuster (MoCo) from comment #34)
However in the our java-based HTML parser code we instead just use the context element for creating the root node (see this comment, added by bug 886390). This means we can't really pop until reaching a HTML namespace when parsing certain HTML elements in a foreign content. I am not sure what would be more tricky, trying to fix this case specifically or just removing this optimization and following the specification more closely.
One issue is what to put in the nsIContent** handle for the first stack node if we don't want to cause the creation of a throw-away DOM node for the HTML element. If we put nullptr there, I can't tell off the top of my head what the consequences would be.
Comment 36•1 year ago
|
||
My vague recollection is that at the time of the optimization, it was valid to check for stack position 0 for the things that were relevant back then. It might be worthwhile to figure out if popping until reaching the HTML namespace check can be augmented with a stack position check.
Comment 38•7 months ago
|
||
Comment 39•4 months ago
|
||
Looks like that change hasn't made it into our tree, yet.
Comment 40•4 months ago
|
||
I managed to try out the changes from https://github.com/validator/htmlparser/pull/115 in our tree and the results look quite promising.
The failures from parser/htmlparser/tests/mochitest/test_html5_tree_construction_part2.html go away when syncing foreign-fragment.dat with upstream.
I am going to wait on bug 2028401 before getting our HTML parser even more out of sync.
Comment 41•2 months ago
|
||
Should be fixed now thanks to the HTML parser update in bug 2028401. Also thanks to sideshowbarker for fixing this upstream.
Description
•