Commit 43bbd38f authored by Xiaocheng Hu's avatar Xiaocheng Hu Committed by Commit Bot

Block copy-and-paste XSS via <noscript>

This patch is analogous to the WebKit patch for a common issue:

https://trac.webkit.org/changeset/254800/webkit

When sanitizing the clipboard markup in a dummy document, we disable
scripting, and parse <noscript> in the script-disabled mode. Then we
parse the sanitized markup in script-enabled mode when inserting it into
the real document. This allows an XSS attack.

This patch introduces a new flag to page settings that allows it to
parse with the scripting flag enabled, while still disabling script
execution. It also renames the |HTMLParserOptions::script_enabled| flag
to |scripting_flag| to improve clarity and match the term in the HTML
spec (https://html.spec.whatwg.org/#scripting-flag).

Bug: 1065761
Change-Id: Ia4bd67a991b354eebd2cbfef6d3291230ddc1f6a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2128839Reviewed-by: default avatarYoshifumi Inoue <yosin@chromium.org>
Reviewed-by: default avatarKent Tamura <tkent@chromium.org>
Commit-Queue: Xiaocheng Hu <xiaochengh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#754969}
parent 48a84a77
...@@ -773,6 +773,8 @@ static Document* CreateStagingDocumentForMarkupSanitization() { ...@@ -773,6 +773,8 @@ static Document* CreateStagingDocumentForMarkupSanitization() {
page->GetSettings().SetScriptEnabled(false); page->GetSettings().SetScriptEnabled(false);
page->GetSettings().SetPluginsEnabled(false); page->GetSettings().SetPluginsEnabled(false);
page->GetSettings().SetAcceleratedCompositingEnabled(false); page->GetSettings().SetAcceleratedCompositingEnabled(false);
page->GetSettings().SetParserScriptingFlagPolicy(
ParserScriptingFlagPolicy::kEnabled);
LocalFrame* frame = MakeGarbageCollected<LocalFrame>( LocalFrame* frame = MakeGarbageCollected<LocalFrame>(
MakeGarbageCollected<EmptyLocalFrameClient>(), *page, MakeGarbageCollected<EmptyLocalFrameClient>(), *page,
......
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
#include "third_party/blink/renderer/core/editing/selection_strategy.h" #include "third_party/blink/renderer/core/editing/selection_strategy.h"
#include "third_party/blink/renderer/core/frame/settings_delegate.h" #include "third_party/blink/renderer/core/frame/settings_delegate.h"
#include "third_party/blink/renderer/core/html/media/autoplay_policy.h" #include "third_party/blink/renderer/core/html/media/autoplay_policy.h"
#include "third_party/blink/renderer/core/html/parser/parser_scripting_flag_policy.h"
#include "third_party/blink/renderer/core/html/track/text_track_kind_user_preference.h" #include "third_party/blink/renderer/core/html/track/text_track_kind_user_preference.h"
#include "third_party/blink/renderer/core/loader/frame_loader_types.h" #include "third_party/blink/renderer/core/loader/frame_loader_types.h"
#include "third_party/blink/renderer/core/settings_macros.h" #include "third_party/blink/renderer/core/settings_macros.h"
......
...@@ -552,6 +552,12 @@ ...@@ -552,6 +552,12 @@
initial: false, initial: false,
}, },
{
name: "parserScriptingFlagPolicy",
initial: "ParserScriptingFlagPolicy::kOnlyIfScriptIsEnabled",
type: "ParserScriptingFlagPolicy"
},
// Forces Android Overlay Scrollbar for mobile emulator. // Forces Android Overlay Scrollbar for mobile emulator.
{ {
name: "forceAndroidOverlayScrollbar", name: "forceAndroidOverlayScrollbar",
......
...@@ -64,6 +64,7 @@ blink_core_sources("parser") { ...@@ -64,6 +64,7 @@ blink_core_sources("parser") {
"input_stream_preprocessor.h", "input_stream_preprocessor.h",
"markup_tokenizer_inlines.h", "markup_tokenizer_inlines.h",
"nesting_level_incrementer.h", "nesting_level_incrementer.h",
"parser_scripting_flag_policy.h",
"parser_synchronization_policy.h", "parser_synchronization_policy.h",
"preload_request.cc", "preload_request.cc",
"preload_request.h", "preload_request.h",
......
...@@ -99,7 +99,7 @@ static HTMLTokenizer::State TokenizerStateForContextElement( ...@@ -99,7 +99,7 @@ static HTMLTokenizer::State TokenizerStateForContextElement(
context_tag.Matches(html_names::kIFrameTag) || context_tag.Matches(html_names::kIFrameTag) ||
context_tag.Matches(html_names::kNoembedTag) || context_tag.Matches(html_names::kNoembedTag) ||
(context_tag.Matches(html_names::kNoscriptTag) && (context_tag.Matches(html_names::kNoscriptTag) &&
options.script_enabled) || options.scripting_flag) ||
context_tag.Matches(html_names::kNoframesTag)) context_tag.Matches(html_names::kNoframesTag))
return report_errors ? HTMLTokenizer::kRAWTEXTState return report_errors ? HTMLTokenizer::kRAWTEXTState
: HTMLTokenizer::kPLAINTEXTState; : HTMLTokenizer::kPLAINTEXTState;
......
...@@ -26,19 +26,20 @@ ...@@ -26,19 +26,20 @@
#include "third_party/blink/renderer/core/html/parser/html_parser_options.h" #include "third_party/blink/renderer/core/html/parser/html_parser_options.h"
#include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
namespace blink { namespace blink {
HTMLParserOptions::HTMLParserOptions(Document* document) { HTMLParserOptions::HTMLParserOptions(Document* document) {
if (!document) if (!document || !document->GetFrame())
return; return;
if (document->GetFrame()) { scripting_flag = (document->GetSettings()->GetParserScriptingFlagPolicy() ==
script_enabled = document->CanExecuteScripts(kNotAboutToExecuteScript); ParserScriptingFlagPolicy::kEnabled) ||
priority_hints_origin_trial_enabled = document->CanExecuteScripts(kNotAboutToExecuteScript);
RuntimeEnabledFeatures::PriorityHintsEnabled(document); priority_hints_origin_trial_enabled =
} RuntimeEnabledFeatures::PriorityHintsEnabled(document);
} }
} // namespace blink } // namespace blink
...@@ -37,7 +37,8 @@ class CORE_EXPORT HTMLParserOptions { ...@@ -37,7 +37,8 @@ class CORE_EXPORT HTMLParserOptions {
DISALLOW_NEW(); DISALLOW_NEW();
public: public:
bool script_enabled = false; // https://html.spec.whatwg.org/#scripting-flag
bool scripting_flag = false;
// TODO(domfarolino): Remove this when Priority Hints is no longer in an // TODO(domfarolino): Remove this when Priority Hints is no longer in an
// Origin Trial. See https://crbug.com/821464. // Origin Trial. See https://crbug.com/821464.
......
...@@ -40,7 +40,7 @@ int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { ...@@ -40,7 +40,7 @@ int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
FuzzedDataProvider fuzzed_data(data, size); FuzzedDataProvider fuzzed_data(data, size);
HTMLParserOptions options; HTMLParserOptions options;
options.script_enabled = fuzzed_data.ConsumeBool(); options.scripting_flag = fuzzed_data.ConsumeBool();
std::unique_ptr<CachedDocumentParameters> document_parameters = std::unique_ptr<CachedDocumentParameters> document_parameters =
CachedDocumentParametersForFuzzing(fuzzed_data); CachedDocumentParametersForFuzzing(fuzzed_data);
......
...@@ -1465,7 +1465,7 @@ void HTMLTokenizer::UpdateStateFor(const String& tag_name) { ...@@ -1465,7 +1465,7 @@ void HTMLTokenizer::UpdateStateFor(const String& tag_name) {
ThreadSafeMatch(tag_name, html_names::kNoembedTag) || ThreadSafeMatch(tag_name, html_names::kNoembedTag) ||
ThreadSafeMatch(tag_name, html_names::kNoframesTag) || ThreadSafeMatch(tag_name, html_names::kNoframesTag) ||
(ThreadSafeMatch(tag_name, html_names::kNoscriptTag) && (ThreadSafeMatch(tag_name, html_names::kNoscriptTag) &&
options_.script_enabled)) options_.scripting_flag))
SetState(HTMLTokenizer::kRAWTEXTState); SetState(HTMLTokenizer::kRAWTEXTState);
} }
......
...@@ -20,7 +20,7 @@ int FuzzTokenizer(const uint8_t* data, size_t size) { ...@@ -20,7 +20,7 @@ int FuzzTokenizer(const uint8_t* data, size_t size) {
// Use the first byte of fuzz data to randomize the tokenizer options. // Use the first byte of fuzz data to randomize the tokenizer options.
HTMLParserOptions options; HTMLParserOptions options;
options.script_enabled = fuzzed_data_provider.ConsumeBool(); options.scripting_flag = fuzzed_data_provider.ConsumeBool();
std::unique_ptr<HTMLTokenizer> tokenizer = std::unique_ptr<HTMLTokenizer> tokenizer =
std::make_unique<HTMLTokenizer>(options); std::make_unique<HTMLTokenizer>(options);
......
...@@ -813,7 +813,7 @@ void HTMLTreeBuilder::ProcessStartTagForInBody(AtomicHTMLToken* token) { ...@@ -813,7 +813,7 @@ void HTMLTreeBuilder::ProcessStartTagForInBody(AtomicHTMLToken* token) {
ProcessGenericRawTextStartTag(token); ProcessGenericRawTextStartTag(token);
return; return;
} }
if (token->GetName() == html_names::kNoscriptTag && options_.script_enabled) { if (token->GetName() == html_names::kNoscriptTag && options_.scripting_flag) {
ProcessGenericRawTextStartTag(token); ProcessGenericRawTextStartTag(token);
return; return;
} }
...@@ -2664,7 +2664,7 @@ bool HTMLTreeBuilder::ProcessStartTagForInHead(AtomicHTMLToken* token) { ...@@ -2664,7 +2664,7 @@ bool HTMLTreeBuilder::ProcessStartTagForInHead(AtomicHTMLToken* token) {
return true; return true;
} }
if (token->GetName() == html_names::kNoscriptTag) { if (token->GetName() == html_names::kNoscriptTag) {
if (options_.script_enabled) { if (options_.scripting_flag) {
ProcessGenericRawTextStartTag(token); ProcessGenericRawTextStartTag(token);
return true; return true;
} }
......
...@@ -191,7 +191,7 @@ HTMLTreeBuilderSimulator::SimulatedToken HTMLTreeBuilderSimulator::Simulate( ...@@ -191,7 +191,7 @@ HTMLTreeBuilderSimulator::SimulatedToken HTMLTreeBuilderSimulator::Simulate(
ThreadSafeMatch(tag_name, html_names::kNoembedTag) || ThreadSafeMatch(tag_name, html_names::kNoembedTag) ||
ThreadSafeMatch(tag_name, html_names::kNoframesTag) || ThreadSafeMatch(tag_name, html_names::kNoframesTag) ||
(ThreadSafeMatch(tag_name, html_names::kNoscriptTag) && (ThreadSafeMatch(tag_name, html_names::kNoscriptTag) &&
options_.script_enabled)) { options_.scripting_flag)) {
tokenizer->SetState(HTMLTokenizer::kRAWTEXTState); tokenizer->SetState(HTMLTokenizer::kRAWTEXTState);
} }
} }
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_PARSER_PARSER_SCRIPTING_FLAG_POLICY_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_PARSER_PARSER_SCRIPTING_FLAG_POLICY_H_
namespace blink {
// Decides whether the scripting flag of the parser should be set to enabled.
// https://html.spec.whatwg.org/#scripting-flag
enum class ParserScriptingFlagPolicy { kOnlyIfScriptIsEnabled, kEnabled };
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_PARSER_PARSER_SCRIPTING_FLAG_POLICY_H_
...@@ -21,4 +21,17 @@ selection_test( ...@@ -21,4 +21,17 @@ selection_test(
}, },
'<div contenteditable>|<svg></svg></div>', '<div contenteditable>|<svg></svg></div>',
'Paste blocks data URI in SVG use elements'); 'Paste blocks data URI in SVG use elements');
// crbug.com/1065761
selection_test(
'<div contenteditable>|</div>',
selection => {
selection.setClipboardData(`
<noscript><u title="</noscript><div contenteditable=false>
<svg>
<use href=data:application/xml;base64,PHN2ZyBpZD0neCcgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJz4KPGEgaHJlZj0namF2YXNjcmlwdDphbGVydCgxMjMpJz4KICAgIDxyZWN0IHdpZHRoPScxMDAlJyBoZWlnaHQ9JzEwMCUnIGZpbGw9J2xpZ2h0Ymx1ZScgLz4KICAgICA8dGV4dCB4PScwJyB5PScwJyBmaWxsPSdibGFjayc+CiAgICAgICA8dHNwYW4geD0nMCcgZHk9JzEuMmVtJz5Pb3BzLCB0aGVyZSdzIHNvbWV0aGluZyB3cm9uZyB3aXRoIHRoZSBwYWdlITwvdHNwYW4+CiAgICAgPHRzcGFuIHg9JzAnIGR5PScxLjJlbSc+UGxlYXNlIGNsaWNrIGhlcmUgdG8gcmVsb2FkLjwvdHNwYW4+Cjwvc3ZnPg==#x>"></noscript>asdasd`);
selection.document.execCommand('paste');
},
'<div contenteditable>|<noscript>&lt;u title="</noscript><div contenteditable="false"><svg></svg></div></div>',
'Paste blocks data URI in SVG use element injection via <noscript>');
</script> </script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment