| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "config.h" |
| 6 // FIXME(dominicc): Poor confused check-webkit-style demands Attribute.h here. |
| 7 #include "core/dom/Attribute.h" |
| 8 |
| 9 #include "core/HTMLNames.h" |
| 10 #include "core/SVGNames.h" |
| 11 #include "core/XLinkNames.h" |
| 12 #include "core/clipboard/Pasteboard.h" |
| 13 #include "core/dom/QualifiedName.h" |
| 14 #include "core/editing/Editor.h" |
| 15 #include "core/editing/SelectionType.h" |
| 16 #include "core/editing/VisibleSelection.h" |
| 17 #include "core/html/HTMLElement.h" |
| 18 #include "core/svg/SVGAElement.h" |
| 19 #include "core/svg/SVGAnimateElement.h" |
| 20 #include "core/svg/SVGDiscardElement.h" |
| 21 #include "core/svg/SVGSetElement.h" |
| 22 #include "core/svg/animation/SVGSMILElement.h" |
| 23 #include "core/svg/properties/SVGPropertyInfo.h" |
| 24 #include "core/testing/DummyPageHolder.h" |
| 25 #include "platform/geometry/IntSize.h" |
| 26 #include "platform/weborigin/KURL.h" |
| 27 #include "wtf/Vector.h" |
| 28 #include "wtf/text/AtomicString.h" |
| 29 #include "wtf/text/WTFString.h" |
| 30 #include <gtest/gtest.h> |
| 31 |
| 32 // Test that SVG content with JavaScript URLs is sanitized by removing |
| 33 // the URLs. This sanitization happens when the content is pasted or |
| 34 // drag-dropped into an editable element. |
| 35 // |
| 36 // There are two vectors for JavaScript URLs in SVG content: |
| 37 // |
| 38 // 1. Attributes, for example xlink:href in an <svg:a> element. |
| 39 // 2. Animations which set those attributes, for example |
| 40 // <animate attributeName="xlink:href" values="javascript:... |
| 41 // |
| 42 // The following SVG elements, although related to animation, cannot |
| 43 // set JavaScript URLs: |
| 44 // |
| 45 // - 'discard' can only remove elements, not set their attributes |
| 46 // - 'animateMotion' does not use attribute name and produces floats |
| 47 // - 'animateTransform' can only animate transform lists |
| 48 |
| 49 namespace blink { |
| 50 |
| 51 // Pastes htmlToPaste into the body of pageHolder's document, and |
| 52 // returns the new content of the body. |
| 53 String contentAfterPastingHTML( |
| 54 DummyPageHolder* pageHolder, |
| 55 const char* htmlToPaste) |
| 56 { |
| 57 LocalFrame& frame = pageHolder->frame(); |
| 58 HTMLElement* body = pageHolder->document().body(); |
| 59 |
| 60 // Make the body editable, and put the caret in it. |
| 61 body->setAttribute(HTMLNames::contenteditableAttr, "true"); |
| 62 frame.selection().setSelection(VisibleSelection::selectionFromContentsOfNode
(body)); |
| 63 EXPECT_EQ(CaretSelection, frame.selection().selectionType()); |
| 64 EXPECT_TRUE(frame.selection().isContentEditable()) << |
| 65 "We should be pasting into something editable."; |
| 66 |
| 67 Pasteboard* pasteboard = Pasteboard::generalPasteboard(); |
| 68 pasteboard->writeHTML(htmlToPaste, blankURL(), "", false); |
| 69 EXPECT_TRUE(frame.editor().executeCommand("Paste")); |
| 70 |
| 71 return body->innerHTML(); |
| 72 } |
| 73 |
| 74 // Integration tests. |
| 75 |
| 76 TEST( |
| 77 UnsafeSVGAttributeSanitizationTest, |
| 78 pasteAnchor_javaScriptHrefIsStripped) |
| 79 { |
| 80 OwnPtr<DummyPageHolder> pageHolder = DummyPageHolder::create(IntSize(1, 1)); |
| 81 static const char unsafeContent[] = |
| 82 "<svg xmlns='http://www.w3.org/2000/svg' " |
| 83 " xmlns:xlink='http://www.w3.org/1999/xlink'" |
| 84 " width='1cm' height='1cm'>" |
| 85 " <a xlink:href='javascript:alert()'></a>" |
| 86 "</svg>"; |
| 87 String sanitizedContent = |
| 88 contentAfterPastingHTML(pageHolder.get(), unsafeContent); |
| 89 |
| 90 EXPECT_TRUE(sanitizedContent.contains("</a>")) << |
| 91 "We should have pasted *something*; the document is: " << |
| 92 sanitizedContent.utf8().data(); |
| 93 EXPECT_FALSE(sanitizedContent.contains(":alert()")) << |
| 94 "The JavaScript URL is unsafe and should have been stripped; " |
| 95 "instead: " << |
| 96 sanitizedContent.utf8().data(); |
| 97 } |
| 98 |
| 99 TEST( |
| 100 UnsafeSVGAttributeSanitizationTest, |
| 101 pasteAnchor_javaScriptHrefIsStripped_caseAndEntityInProtocol) |
| 102 { |
| 103 OwnPtr<DummyPageHolder> pageHolder = DummyPageHolder::create(IntSize(1, 1)); |
| 104 static const char unsafeContent[] = |
| 105 "<svg xmlns='http://www.w3.org/2000/svg' " |
| 106 " xmlns:xlink='http://www.w3.org/1999/xlink'" |
| 107 " width='1cm' height='1cm'>" |
| 108 " <a xlink:href='jAvascriPT:alert()'></a>" |
| 109 "</svg>"; |
| 110 String sanitizedContent = |
| 111 contentAfterPastingHTML(pageHolder.get(), unsafeContent); |
| 112 |
| 113 EXPECT_TRUE(sanitizedContent.contains("</a>")) << |
| 114 "We should have pasted *something*; the document is: " << |
| 115 sanitizedContent.utf8().data(); |
| 116 EXPECT_FALSE(sanitizedContent.contains(":alert()")) << |
| 117 "The JavaScript URL is unsafe and should have been stripped; " |
| 118 "instead: " << |
| 119 sanitizedContent.utf8().data(); |
| 120 } |
| 121 |
| 122 TEST( |
| 123 UnsafeSVGAttributeSanitizationTest, |
| 124 pasteAnchor_javaScriptHrefIsStripped_entityWithoutSemicolonInProtocol) |
| 125 { |
| 126 OwnPtr<DummyPageHolder> pageHolder = DummyPageHolder::create(IntSize(1, 1)); |
| 127 static const char unsafeContent[] = |
| 128 "<svg xmlns='http://www.w3.org/2000/svg' " |
| 129 " xmlns:xlink='http://www.w3.org/1999/xlink'" |
| 130 " width='1cm' height='1cm'>" |
| 131 " <a xlink:href='javascript:alert()'></a>" |
| 132 "</svg>"; |
| 133 String sanitizedContent = |
| 134 contentAfterPastingHTML(pageHolder.get(), unsafeContent); |
| 135 |
| 136 EXPECT_TRUE(sanitizedContent.contains("</a>")) << |
| 137 "We should have pasted *something*; the document is: " << |
| 138 sanitizedContent.utf8().data(); |
| 139 EXPECT_FALSE(sanitizedContent.contains(":alert()")) << |
| 140 "The JavaScript URL is unsafe and should have been stripped; " |
| 141 "instead: " << |
| 142 sanitizedContent.utf8().data(); |
| 143 } |
| 144 |
| 145 // Other sanitization integration tests are layout tests that use |
| 146 // document.execCommand('Copy') to source content that they later |
| 147 // paste. However SVG animation elements are not serialized when |
| 148 // copying, which means we can't test sanitizing these attributes in |
| 149 // layout tests: there is nowhere to source the unsafe content from. |
| 150 TEST( |
| 151 UnsafeSVGAttributeSanitizationTest, |
| 152 pasteAnimatedAnchor_javaScriptHrefIsStripped_caseAndEntityInProtocol) |
| 153 { |
| 154 OwnPtr<DummyPageHolder> pageHolder = DummyPageHolder::create(IntSize(1, 1)); |
| 155 static const char unsafeContent[] = |
| 156 "<svg xmlns='http://www.w3.org/2000/svg' " |
| 157 " xmlns:xlink='http://www.w3.org/1999/xlink'" |
| 158 " width='1cm' height='1cm'>" |
| 159 " <a xlink:href='https://www.google.com/'>" |
| 160 " <animate xmlns:ng='http://www.w3.org/1999/xlink' " |
| 161 " attributeName='ng:href' values='evil;JaVaSCRIpT:alert(
)'>" |
| 162 " </a>" |
| 163 "</svg>"; |
| 164 String sanitizedContent = |
| 165 contentAfterPastingHTML(pageHolder.get(), unsafeContent); |
| 166 |
| 167 EXPECT_TRUE(sanitizedContent.contains("<a xlink:href=\"https://www.goo")) << |
| 168 "We should have pasted *something*; the document is: " << |
| 169 sanitizedContent.utf8().data(); |
| 170 EXPECT_FALSE(sanitizedContent.contains(":alert()")) << |
| 171 "The JavaScript URL is unsafe and should have been stripped; " |
| 172 "instead: " << |
| 173 sanitizedContent.utf8().data(); |
| 174 } |
| 175 |
| 176 // Unit tests |
| 177 |
| 178 // stripScriptingAttributes inspects animation attributes for |
| 179 // javascript: URLs. This check could be defeated if strings supported |
| 180 // addition. If this test starts failing you must strengthen |
| 181 // Element::stripScriptingAttributes, perhaps to strip all |
| 182 // SVG animation attributes. |
| 183 TEST(UnsafeSVGAttributeSanitizationTest, stringsShouldNotSupportAddition) |
| 184 { |
| 185 RefPtrWillBeRawPtr<Document> document = Document::create(); |
| 186 RefPtrWillBeRawPtr<SVGElement> target = SVGAElement::create(*document); |
| 187 RefPtrWillBeRawPtr<SVGAnimateElement> element = SVGAnimateElement::create(*d
ocument); |
| 188 element->setTargetElement(target.get()); |
| 189 element->setAttributeName(XLinkNames::hrefAttr); |
| 190 |
| 191 // Sanity check that xlink:href was identified as a "string" attribute |
| 192 EXPECT_EQ(AnimatedString, element->animatedPropertyType()); |
| 193 |
| 194 EXPECT_FALSE(element->animatedPropertyTypeSupportsAddition()); |
| 195 } |
| 196 |
| 197 TEST( |
| 198 UnsafeSVGAttributeSanitizationTest, |
| 199 stripScriptingAttributes_animateElement) |
| 200 { |
| 201 Vector<Attribute> attributes; |
| 202 attributes.append(Attribute(XLinkNames::hrefAttr, "javascript:alert()")); |
| 203 attributes.append(Attribute(SVGNames::fromAttr, "/home")); |
| 204 attributes.append(Attribute(SVGNames::toAttr, "javascript:own3d()")); |
| 205 |
| 206 RefPtrWillBeRawPtr<Document> document = Document::create(); |
| 207 RefPtrWillBeRawPtr<Element> element = SVGAnimateElement::create(*document); |
| 208 element->stripScriptingAttributes(attributes); |
| 209 |
| 210 EXPECT_EQ(2ul, attributes.size()) << |
| 211 "One of the attributes should have been stripped."; |
| 212 EXPECT_EQ(XLinkNames::hrefAttr, attributes[0].name()) << |
| 213 "The 'xlink:href' attribute should not have been stripped from " |
| 214 "<animate> because it is not a URL attribute of <animate>."; |
| 215 EXPECT_EQ(SVGNames::fromAttr, attributes[1].name()) << |
| 216 "The 'from' attribute should not have been strippef from <animate> " |
| 217 "because its value is innocuous."; |
| 218 } |
| 219 |
| 220 TEST( |
| 221 UnsafeSVGAttributeSanitizationTest, |
| 222 isJavaScriptURLAttribute_xlinkHrefContainingJavascriptURL) |
| 223 { |
| 224 Attribute attribute(XLinkNames::hrefAttr, "javascript:alert()"); |
| 225 RefPtrWillBeRawPtr<Document> document = Document::create(); |
| 226 RefPtrWillBeRawPtr<Element> element = SVGAElement::create(*document); |
| 227 EXPECT_TRUE( |
| 228 element->isJavaScriptURLAttribute(attribute)) << |
| 229 "The 'a' element should identify an 'xlink:href' attribute with a " |
| 230 "JavaScript URL value as a JavaScript URL attribute"; |
| 231 } |
| 232 |
| 233 TEST( |
| 234 UnsafeSVGAttributeSanitizationTest, |
| 235 isJavaScriptURLAttribute_xlinkHrefContainingJavascriptURL_alternatePrefix) |
| 236 { |
| 237 QualifiedName hrefAlternatePrefix( |
| 238 "foo", "href", XLinkNames::xlinkNamespaceURI); |
| 239 Attribute evilAttribute(hrefAlternatePrefix, "javascript:alert()"); |
| 240 RefPtrWillBeRawPtr<Document> document = Document::create(); |
| 241 RefPtrWillBeRawPtr<Element> element = SVGAElement::create(*document); |
| 242 EXPECT_TRUE(element->isJavaScriptURLAttribute(evilAttribute)) << |
| 243 "The XLink 'href' attribute with a JavaScript URL value should be " |
| 244 "identified as a JavaScript URL attribute, even if the attribute " |
| 245 "doesn't use the typical 'xlink' prefix."; |
| 246 } |
| 247 |
| 248 TEST( |
| 249 UnsafeSVGAttributeSanitizationTest, |
| 250 isSVGAnimationAttributeSettingJavaScriptURL_fromContainingJavaScriptURL) |
| 251 { |
| 252 Attribute evilAttribute(SVGNames::fromAttr, "javascript:alert()"); |
| 253 RefPtrWillBeRawPtr<Document> document = Document::create(); |
| 254 RefPtrWillBeRawPtr<Element> element = SVGAnimateElement::create(*document); |
| 255 EXPECT_TRUE( |
| 256 element->isSVGAnimationAttributeSettingJavaScriptURL(evilAttribute)) << |
| 257 "The animate element should identify a 'from' attribute with a " |
| 258 "JavaScript URL value as setting a JavaScript URL."; |
| 259 } |
| 260 |
| 261 TEST( |
| 262 UnsafeSVGAttributeSanitizationTest, |
| 263 isSVGAnimationAttributeSettingJavaScriptURL_toContainingJavaScripURL) |
| 264 { |
| 265 Attribute evilAttribute(SVGNames::toAttr, "javascript:window.close()"); |
| 266 RefPtrWillBeRawPtr<Document> document = Document::create(); |
| 267 RefPtrWillBeRawPtr<Element> element = SVGSetElement::create(*document); |
| 268 EXPECT_TRUE( |
| 269 element->isSVGAnimationAttributeSettingJavaScriptURL(evilAttribute)) << |
| 270 "The set element should identify a 'to' attribute with a JavaScript " |
| 271 "URL value as setting a JavaScript URL."; |
| 272 } |
| 273 |
| 274 TEST( |
| 275 UnsafeSVGAttributeSanitizationTest, |
| 276 isSVGAnimationAttributeSettingJavaScriptURL_valuesContainingJavaScriptURL) |
| 277 { |
| 278 Attribute evilAttribute(SVGNames::valuesAttr, "hi!; javascript:confirm()"); |
| 279 RefPtrWillBeRawPtr<Document> document = Document::create(); |
| 280 RefPtrWillBeRawPtr<Element> element = SVGAnimateElement::create(*document); |
| 281 element = SVGAnimateElement::create(*document); |
| 282 EXPECT_TRUE( |
| 283 element->isSVGAnimationAttributeSettingJavaScriptURL(evilAttribute)) << |
| 284 "The animate element should identify a 'values' attribute with a " |
| 285 "JavaScript URL value as setting a JavaScript URL."; |
| 286 } |
| 287 |
| 288 TEST( |
| 289 UnsafeSVGAttributeSanitizationTest, |
| 290 isSVGAnimationAttributeSettingJavaScriptURL_innocuousAnimationAttribute) |
| 291 { |
| 292 Attribute fineAttribute(SVGNames::fromAttr, "hello, world!"); |
| 293 RefPtrWillBeRawPtr<Document> document = Document::create(); |
| 294 RefPtrWillBeRawPtr<Element> element = SVGSetElement::create(*document); |
| 295 EXPECT_FALSE( |
| 296 element->isSVGAnimationAttributeSettingJavaScriptURL(fineAttribute)) << |
| 297 "The animate element should not identify a 'from' attribute with an " |
| 298 "innocuous value as setting a JavaScript URL."; |
| 299 } |
| 300 |
| 301 } // namespace blink |
| OLD | NEW |