// Copyright 2015 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. // FIXME(dominicc): Poor confused check-webkit-style demands Attribute.h here. #include "third_party/blink/renderer/core/dom/attribute.h" #include #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/common/browser_interface_broker_proxy.h" #include "third_party/blink/renderer/core/clipboard/system_clipboard.h" #include "third_party/blink/renderer/core/dom/qualified_name.h" #include "third_party/blink/renderer/core/editing/editor.h" #include "third_party/blink/renderer/core/editing/frame_selection.h" #include "third_party/blink/renderer/core/editing/selection_template.h" #include "third_party/blink/renderer/core/editing/selection_type.h" #include "third_party/blink/renderer/core/editing/visible_selection.h" #include "third_party/blink/renderer/core/html/html_element.h" #include "third_party/blink/renderer/core/html_names.h" #include "third_party/blink/renderer/core/svg/animation/svg_smil_element.h" #include "third_party/blink/renderer/core/svg/properties/svg_property_info.h" #include "third_party/blink/renderer/core/svg/svg_a_element.h" #include "third_party/blink/renderer/core/svg/svg_animate_element.h" #include "third_party/blink/renderer/core/svg/svg_set_element.h" #include "third_party/blink/renderer/core/svg_names.h" #include "third_party/blink/renderer/core/testing/dummy_page_holder.h" #include "third_party/blink/renderer/core/testing/mock_clipboard_host.h" #include "third_party/blink/renderer/core/testing/page_test_base.h" #include "third_party/blink/renderer/core/xlink_names.h" #include "third_party/blink/renderer/platform/geometry/int_size.h" #include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" #include "third_party/blink/renderer/platform/weborigin/kurl.h" #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" #include "third_party/blink/renderer/platform/wtf/vector.h" // Test that SVG content with JavaScript URLs is sanitized by removing // the URLs. This sanitization happens when the content is pasted or // drag-dropped into an editable element. // // There are two vectors for JavaScript URLs in SVG content: // // 1. Attributes, for example xlink:href/href in an element. // 2. Animations which set those attributes, for example // focus(); frame.GetDocument()->UpdateStyleAndLayout(DocumentUpdateReason::kTest); frame.Selection().SetSelectionAndEndTyping( SelectionInDOMTree::Builder().SelectAllChildren(*body).Build()); EXPECT_TRUE(frame.Selection().ComputeVisibleSelectionInDOMTree().IsCaret()); EXPECT_TRUE( frame.Selection().ComputeVisibleSelectionInDOMTree().IsContentEditable()) << "We should be pasting into something editable."; frame.GetSystemClipboard()->WriteHTML(html_to_paste, BlankURL(), "", SystemClipboard::kCannotSmartReplace); frame.GetSystemClipboard()->CommitWrite(); // Run all tasks in a message loop to allow asynchronous clipboard writing // to happen before reading from it synchronously. test::RunPendingTasks(); EXPECT_TRUE(frame.GetEditor().ExecuteCommand("Paste")); return body->innerHTML(); } // Integration tests. TEST(UnsafeSVGAttributeSanitizationTest, pasteAnchor_javaScriptHrefIsStripped) { auto page_holder = std::make_unique(IntSize(1, 1)); static const char kUnsafeContent[] = "" " " ""; String sanitized_content = ContentAfterPastingHTML(page_holder.get(), kUnsafeContent); EXPECT_TRUE(sanitized_content.Contains("")) << "We should have pasted *something*; the document is: " << sanitized_content.Utf8(); EXPECT_FALSE(sanitized_content.Contains(":alert()")) << "The JavaScript URL is unsafe and should have been stripped; " "instead: " << sanitized_content.Utf8(); } TEST(UnsafeSVGAttributeSanitizationTest, pasteAnchor_javaScriptXlinkHrefIsStripped) { auto page_holder = std::make_unique(IntSize(1, 1)); static const char kUnsafeContent[] = "" " " ""; String sanitized_content = ContentAfterPastingHTML(page_holder.get(), kUnsafeContent); EXPECT_TRUE(sanitized_content.Contains("")) << "We should have pasted *something*; the document is: " << sanitized_content.Utf8(); EXPECT_FALSE(sanitized_content.Contains(":alert()")) << "The JavaScript URL is unsafe and should have been stripped; " "instead: " << sanitized_content.Utf8(); } TEST(UnsafeSVGAttributeSanitizationTest, pasteAnchor_javaScriptHrefIsStripped_caseAndEntityInProtocol) { auto page_holder = std::make_unique(IntSize(1, 1)); static const char kUnsafeContent[] = "" " " ""; String sanitized_content = ContentAfterPastingHTML(page_holder.get(), kUnsafeContent); EXPECT_TRUE(sanitized_content.Contains("")) << "We should have pasted *something*; the document is: " << sanitized_content.Utf8(); EXPECT_FALSE(sanitized_content.Contains(":alert()")) << "The JavaScript URL is unsafe and should have been stripped; " "instead: " << sanitized_content.Utf8(); } TEST(UnsafeSVGAttributeSanitizationTest, pasteAnchor_javaScriptXlinkHrefIsStripped_caseAndEntityInProtocol) { auto page_holder = std::make_unique(IntSize(1, 1)); static const char kUnsafeContent[] = "" " " ""; String sanitized_content = ContentAfterPastingHTML(page_holder.get(), kUnsafeContent); EXPECT_TRUE(sanitized_content.Contains("")) << "We should have pasted *something*; the document is: " << sanitized_content.Utf8(); EXPECT_FALSE(sanitized_content.Contains(":alert()")) << "The JavaScript URL is unsafe and should have been stripped; " "instead: " << sanitized_content.Utf8(); } TEST(UnsafeSVGAttributeSanitizationTest, pasteAnchor_javaScriptHrefIsStripped_entityWithoutSemicolonInProtocol) { auto page_holder = std::make_unique(IntSize(1, 1)); static const char kUnsafeContent[] = "" " " ""; String sanitized_content = ContentAfterPastingHTML(page_holder.get(), kUnsafeContent); EXPECT_TRUE(sanitized_content.Contains("")) << "We should have pasted *something*; the document is: " << sanitized_content.Utf8(); EXPECT_FALSE(sanitized_content.Contains(":alert()")) << "The JavaScript URL is unsafe and should have been stripped; " "instead: " << sanitized_content.Utf8(); } TEST( UnsafeSVGAttributeSanitizationTest, pasteAnchor_javaScriptXlinkHrefIsStripped_entityWithoutSemicolonInProtocol) { auto page_holder = std::make_unique(IntSize(1, 1)); static const char kUnsafeContent[] = "" " " ""; String sanitized_content = ContentAfterPastingHTML(page_holder.get(), kUnsafeContent); EXPECT_TRUE(sanitized_content.Contains("")) << "We should have pasted *something*; the document is: " << sanitized_content.Utf8(); EXPECT_FALSE(sanitized_content.Contains(":alert()")) << "The JavaScript URL is unsafe and should have been stripped; " "instead: " << sanitized_content.Utf8(); } // Other sanitization integration tests are web tests that use // document.execCommand('Copy') to source content that they later // paste. However SVG animation elements are not serialized when // copying, which means we can't test sanitizing these attributes in // web tests: there is nowhere to source the unsafe content from. TEST(UnsafeSVGAttributeSanitizationTest, pasteAnimatedAnchor_javaScriptHrefIsStripped_caseAndEntityInProtocol) { auto page_holder = std::make_unique(IntSize(1, 1)); static const char kUnsafeContent[] = "" " " " " " " ""; String sanitized_content = ContentAfterPastingHTML(page_holder.get(), kUnsafeContent); EXPECT_TRUE(sanitized_content.Contains("(IntSize(1, 1)); static const char kUnsafeContent[] = "" " " " " " " ""; String sanitized_content = ContentAfterPastingHTML(page_holder.get(), kUnsafeContent); EXPECT_TRUE(sanitized_content.Contains("(*document); auto* element = MakeGarbageCollected(*document); element->SetTargetElement(target); element->SetAttributeName(xlink_names::kHrefAttr); // Sanity check that xlink:href was identified as a "string" attribute EXPECT_EQ(kAnimatedString, element->GetAnimatedPropertyType()); EXPECT_FALSE(element->AnimatedPropertyTypeSupportsAddition()); element->SetAttributeName(svg_names::kHrefAttr); // Sanity check that href was identified as a "string" attribute EXPECT_EQ(kAnimatedString, element->GetAnimatedPropertyType()); EXPECT_FALSE(element->AnimatedPropertyTypeSupportsAddition()); } TEST(UnsafeSVGAttributeSanitizationTest, stripScriptingAttributes_animateElement) { Vector attributes; attributes.push_back(Attribute(xlink_names::kHrefAttr, "javascript:alert()")); attributes.push_back(Attribute(svg_names::kHrefAttr, "javascript:alert()")); attributes.push_back(Attribute(svg_names::kFromAttr, "/home")); attributes.push_back(Attribute(svg_names::kToAttr, "javascript:own3d()")); auto* document = Document::CreateForTest(); auto* element = MakeGarbageCollected(*document); element->StripScriptingAttributes(attributes); EXPECT_EQ(3ul, attributes.size()) << "One of the attributes should have been stripped."; EXPECT_EQ(xlink_names::kHrefAttr, attributes[0].GetName()) << "The 'xlink:href' attribute should not have been stripped from " " because it is not a URL attribute of ."; EXPECT_EQ(svg_names::kHrefAttr, attributes[1].GetName()) << "The 'href' attribute should not have been stripped from " " because it is not a URL attribute of ."; EXPECT_EQ(svg_names::kFromAttr, attributes[2].GetName()) << "The 'from' attribute should not have been strippef from " "because its value is innocuous."; } TEST(UnsafeSVGAttributeSanitizationTest, isJavaScriptURLAttribute_hrefContainingJavascriptURL) { Attribute attribute(svg_names::kHrefAttr, "javascript:alert()"); auto* document = Document::CreateForTest(); auto* element = MakeGarbageCollected(*document); EXPECT_TRUE(element->IsJavaScriptURLAttribute(attribute)) << "The 'a' element should identify an 'href' attribute with a " "JavaScript URL value as a JavaScript URL attribute"; } TEST(UnsafeSVGAttributeSanitizationTest, isJavaScriptURLAttribute_xlinkHrefContainingJavascriptURL) { Attribute attribute(xlink_names::kHrefAttr, "javascript:alert()"); auto* document = Document::CreateForTest(); auto* element = MakeGarbageCollected(*document); EXPECT_TRUE(element->IsJavaScriptURLAttribute(attribute)) << "The 'a' element should identify an 'xlink:href' attribute with a " "JavaScript URL value as a JavaScript URL attribute"; } TEST( UnsafeSVGAttributeSanitizationTest, isJavaScriptURLAttribute_xlinkHrefContainingJavascriptURL_alternatePrefix) { QualifiedName href_alternate_prefix("foo", "href", xlink_names::kNamespaceURI); Attribute evil_attribute(href_alternate_prefix, "javascript:alert()"); auto* document = Document::CreateForTest(); auto* element = MakeGarbageCollected(*document); EXPECT_TRUE(element->IsJavaScriptURLAttribute(evil_attribute)) << "The XLink 'href' attribute with a JavaScript URL value should be " "identified as a JavaScript URL attribute, even if the attribute " "doesn't use the typical 'xlink' prefix."; } TEST(UnsafeSVGAttributeSanitizationTest, isSVGAnimationAttributeSettingJavaScriptURL_fromContainingJavaScriptURL) { Attribute evil_attribute(svg_names::kFromAttr, "javascript:alert()"); auto* document = Document::CreateForTest(); auto* element = MakeGarbageCollected(*document); EXPECT_TRUE( element->IsSVGAnimationAttributeSettingJavaScriptURL(evil_attribute)) << "The animate element should identify a 'from' attribute with a " "JavaScript URL value as setting a JavaScript URL."; } TEST(UnsafeSVGAttributeSanitizationTest, isSVGAnimationAttributeSettingJavaScriptURL_toContainingJavaScripURL) { Attribute evil_attribute(svg_names::kToAttr, "javascript:window.close()"); auto* document = Document::CreateForTest(); auto* element = MakeGarbageCollected(*document); EXPECT_TRUE( element->IsSVGAnimationAttributeSettingJavaScriptURL(evil_attribute)) << "The set element should identify a 'to' attribute with a JavaScript " "URL value as setting a JavaScript URL."; } TEST( UnsafeSVGAttributeSanitizationTest, isSVGAnimationAttributeSettingJavaScriptURL_valuesContainingJavaScriptURL) { Attribute evil_attribute(svg_names::kValuesAttr, "hi!; javascript:confirm()"); auto* document = Document::CreateForTest(); auto* element = MakeGarbageCollected(*document); EXPECT_TRUE( element->IsSVGAnimationAttributeSettingJavaScriptURL(evil_attribute)) << "The animate element should identify a 'values' attribute with a " "JavaScript URL value as setting a JavaScript URL."; } TEST(UnsafeSVGAttributeSanitizationTest, isSVGAnimationAttributeSettingJavaScriptURL_innocuousAnimationAttribute) { Attribute fine_attribute(svg_names::kFromAttr, "hello, world!"); auto* document = Document::CreateForTest(); auto* element = MakeGarbageCollected(*document); EXPECT_FALSE( element->IsSVGAnimationAttributeSettingJavaScriptURL(fine_attribute)) << "The animate element should not identify a 'from' attribute with an " "innocuous value as setting a JavaScript URL."; } } // namespace blink