| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2011 Adam Barth. All Rights Reserved. | 2 * Copyright (C) 2011 Adam Barth. All Rights Reserved. |
| 3 * Copyright (C) 2011 Daniel Bates (dbates@intudata.com). | 3 * Copyright (C) 2011 Daniel Bates (dbates@intudata.com). |
| 4 * | 4 * |
| 5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
| 6 * modification, are permitted provided that the following conditions | 6 * modification, are permitted provided that the following conditions |
| 7 * are met: | 7 * are met: |
| 8 * 1. Redistributions of source code must retain the above copyright | 8 * 1. Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * 2. Redistributions in binary form must reproduce the above copyright | 10 * 2. Redistributions in binary form must reproduce the above copyright |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 43 #include "core/loader/DocumentLoader.h" | 43 #include "core/loader/DocumentLoader.h" |
| 44 #include "core/loader/MixedContentChecker.h" | 44 #include "core/loader/MixedContentChecker.h" |
| 45 #include "platform/network/EncodedFormData.h" | 45 #include "platform/network/EncodedFormData.h" |
| 46 #include "platform/text/DecodeEscapeSequences.h" | 46 #include "platform/text/DecodeEscapeSequences.h" |
| 47 #include "wtf/ASCIICType.h" | 47 #include "wtf/ASCIICType.h" |
| 48 #include "wtf/PtrUtil.h" | 48 #include "wtf/PtrUtil.h" |
| 49 #include <memory> | 49 #include <memory> |
| 50 | 50 |
| 51 namespace { | 51 namespace { |
| 52 | 52 |
| 53 // SecurityOrigin::urlWithUniqueSecurityOrigin() can't be used cross-thread, or
we'd use it instead. | 53 // SecurityOrigin::urlWithUniqueSecurityOrigin() can't be used cross-thread, or |
| 54 // we'd use it instead. |
| 54 const char kURLWithUniqueOrigin[] = "data:,"; | 55 const char kURLWithUniqueOrigin[] = "data:,"; |
| 55 | 56 |
| 56 const char kSafeJavaScriptURL[] = "javascript:void(0)"; | 57 const char kSafeJavaScriptURL[] = "javascript:void(0)"; |
| 57 | 58 |
| 58 } // namespace | 59 } // namespace |
| 59 | 60 |
| 60 namespace blink { | 61 namespace blink { |
| 61 | 62 |
| 62 using namespace HTMLNames; | 63 using namespace HTMLNames; |
| 63 | 64 |
| 64 static bool isNonCanonicalCharacter(UChar c) { | 65 static bool isNonCanonicalCharacter(UChar c) { |
| 65 // We remove all non-ASCII characters, including non-printable ASCII character
s. | 66 // We remove all non-ASCII characters, including non-printable ASCII |
| 67 // characters. |
| 66 // | 68 // |
| 67 // Note, we don't remove backslashes like PHP stripslashes(), which among othe
r things converts "\\0" to the \0 character. | 69 // Note, we don't remove backslashes like PHP stripslashes(), which among |
| 68 // Instead, we remove backslashes and zeros (since the string "\\0" =(remove b
ackslashes)=> "0"). However, this has the | 70 // other things converts "\\0" to the \0 character. Instead, we remove |
| 69 // adverse effect that we remove any legitimate zeros from a string. | 71 // backslashes and zeros (since the string "\\0" =(remove backslashes)=> "0"). |
| 72 // However, this has the adverse effect that we remove any legitimate zeros |
| 73 // from a string. |
| 70 // | 74 // |
| 71 // We also remove forward-slash, because it is common for some servers to coll
apse successive path components, eg, | 75 // We also remove forward-slash, because it is common for some servers to |
| 72 // a//b becomes a/b. | 76 // collapse successive path components, eg, a//b becomes a/b. |
| 73 // | 77 // |
| 74 // We also remove the questionmark character, since some severs replace invali
d high-bytes with a questionmark. We | 78 // We also remove the questionmark character, since some severs replace |
| 75 // are already stripping the high-bytes so we also strip the questionmark to m
atch. | 79 // invalid high-bytes with a questionmark. We are already stripping the |
| 80 // high-bytes so we also strip the questionmark to match. |
| 76 // | 81 // |
| 77 // We also move the percent character, since some servers strip it when there'
s a malformed sequence. | 82 // We also move the percent character, since some servers strip it when |
| 83 // there's a malformed sequence. |
| 78 // | 84 // |
| 79 // For instance: new String("http://localhost:8000?x") => new String("http:loc
alhost:8x"). | 85 // For instance: new String("http://localhost:8000?x") => new |
| 86 // String("http:localhost:8x"). |
| 80 return (c == '\\' || c == '0' || c == '\0' || c == '/' || c == '?' || | 87 return (c == '\\' || c == '0' || c == '\0' || c == '/' || c == '?' || |
| 81 c == '%' || c >= 127); | 88 c == '%' || c >= 127); |
| 82 } | 89 } |
| 83 | 90 |
| 84 static bool isRequiredForInjection(UChar c) { | 91 static bool isRequiredForInjection(UChar c) { |
| 85 return (c == '\'' || c == '"' || c == '<' || c == '>'); | 92 return (c == '\'' || c == '"' || c == '<' || c == '>'); |
| 86 } | 93 } |
| 87 | 94 |
| 88 static bool isTerminatingCharacter(UChar c) { | 95 static bool isTerminatingCharacter(UChar c) { |
| 89 return (c == '&' || c == '/' || c == '"' || c == '\'' || c == '<' || | 96 return (c == '&' || c == '/' || c == '"' || c == '\'' || c == '<' || |
| (...skipping 27 matching lines...) Expand all Loading... |
| 117 | 124 |
| 118 static bool startsOpeningScriptTagAt(const String& string, size_t start) { | 125 static bool startsOpeningScriptTagAt(const String& string, size_t start) { |
| 119 if (start + 6 >= string.length()) | 126 if (start + 6 >= string.length()) |
| 120 return false; | 127 return false; |
| 121 // TODO(esprehn): StringView should probably have startsWith. | 128 // TODO(esprehn): StringView should probably have startsWith. |
| 122 StringView script("<script"); | 129 StringView script("<script"); |
| 123 return equalIgnoringASCIICase(StringView(string, start, script.length()), | 130 return equalIgnoringASCIICase(StringView(string, start, script.length()), |
| 124 script); | 131 script); |
| 125 } | 132 } |
| 126 | 133 |
| 127 // If other files need this, we should move this to core/html/parser/HTMLParserI
dioms.h | 134 // If other files need this, we should move this to |
| 135 // core/html/parser/HTMLParserIdioms.h |
| 128 template <size_t inlineCapacity> | 136 template <size_t inlineCapacity> |
| 129 bool threadSafeMatch(const Vector<UChar, inlineCapacity>& vector, | 137 bool threadSafeMatch(const Vector<UChar, inlineCapacity>& vector, |
| 130 const QualifiedName& qname) { | 138 const QualifiedName& qname) { |
| 131 return equalIgnoringNullity(vector, qname.localName().impl()); | 139 return equalIgnoringNullity(vector, qname.localName().impl()); |
| 132 } | 140 } |
| 133 | 141 |
| 134 static bool hasName(const HTMLToken& token, const QualifiedName& name) { | 142 static bool hasName(const HTMLToken& token, const QualifiedName& name) { |
| 135 return threadSafeMatch(token.name(), name); | 143 return threadSafeMatch(token.name(), name); |
| 136 } | 144 } |
| 137 | 145 |
| 138 static bool findAttributeWithName(const HTMLToken& token, | 146 static bool findAttributeWithName(const HTMLToken& token, |
| 139 const QualifiedName& name, | 147 const QualifiedName& name, |
| 140 size_t& indexOfMatchingAttribute) { | 148 size_t& indexOfMatchingAttribute) { |
| 141 // Notice that we're careful not to ref the StringImpl here because we might b
e on a background thread. | 149 // Notice that we're careful not to ref the StringImpl here because we might |
| 150 // be on a background thread. |
| 142 const String& attrName = name.namespaceURI() == XLinkNames::xlinkNamespaceURI | 151 const String& attrName = name.namespaceURI() == XLinkNames::xlinkNamespaceURI |
| 143 ? "xlink:" + name.localName().getString() | 152 ? "xlink:" + name.localName().getString() |
| 144 : name.localName().getString(); | 153 : name.localName().getString(); |
| 145 | 154 |
| 146 for (size_t i = 0; i < token.attributes().size(); ++i) { | 155 for (size_t i = 0; i < token.attributes().size(); ++i) { |
| 147 if (equalIgnoringNullity(token.attributes().at(i).nameAsVector(), | 156 if (equalIgnoringNullity(token.attributes().at(i).nameAsVector(), |
| 148 attrName)) { | 157 attrName)) { |
| 149 indexOfMatchingAttribute = i; | 158 indexOfMatchingAttribute = i; |
| 150 return true; | 159 return true; |
| 151 } | 160 } |
| 152 } | 161 } |
| 153 return false; | 162 return false; |
| 154 } | 163 } |
| 155 | 164 |
| 156 static bool isNameOfInlineEventHandler(const Vector<UChar, 32>& name) { | 165 static bool isNameOfInlineEventHandler(const Vector<UChar, 32>& name) { |
| 157 const size_t lengthOfShortestInlineEventHandlerName = 5; // To wit: oncut. | 166 const size_t lengthOfShortestInlineEventHandlerName = 5; // To wit: oncut. |
| 158 if (name.size() < lengthOfShortestInlineEventHandlerName) | 167 if (name.size() < lengthOfShortestInlineEventHandlerName) |
| 159 return false; | 168 return false; |
| 160 return name[0] == 'o' && name[1] == 'n'; | 169 return name[0] == 'o' && name[1] == 'n'; |
| 161 } | 170 } |
| 162 | 171 |
| 163 static bool isDangerousHTTPEquiv(const String& value) { | 172 static bool isDangerousHTTPEquiv(const String& value) { |
| 164 String equiv = value.stripWhiteSpace(); | 173 String equiv = value.stripWhiteSpace(); |
| 165 return equalIgnoringCase(equiv, "refresh") || | 174 return equalIgnoringCase(equiv, "refresh") || |
| 166 equalIgnoringCase(equiv, "set-cookie"); | 175 equalIgnoringCase(equiv, "set-cookie"); |
| 167 } | 176 } |
| 168 | 177 |
| 169 static inline String decode16BitUnicodeEscapeSequences(const String& string) { | 178 static inline String decode16BitUnicodeEscapeSequences(const String& string) { |
| 170 // Note, the encoding is ignored since each %u-escape sequence represents a UT
F-16 code unit. | 179 // Note, the encoding is ignored since each %u-escape sequence represents a |
| 180 // UTF-16 code unit. |
| 171 return decodeEscapeSequences<Unicode16BitEscapeSequence>(string, | 181 return decodeEscapeSequences<Unicode16BitEscapeSequence>(string, |
| 172 UTF8Encoding()); | 182 UTF8Encoding()); |
| 173 } | 183 } |
| 174 | 184 |
| 175 static inline String decodeStandardURLEscapeSequences( | 185 static inline String decodeStandardURLEscapeSequences( |
| 176 const String& string, | 186 const String& string, |
| 177 const WTF::TextEncoding& encoding) { | 187 const WTF::TextEncoding& encoding) { |
| 178 // We use decodeEscapeSequences() instead of decodeURLEscapeSequences() (decla
red in weborigin/KURL.h) to | 188 // We use decodeEscapeSequences() instead of decodeURLEscapeSequences() |
| 179 // avoid platform-specific URL decoding differences (e.g. KURLGoogle). | 189 // (declared in weborigin/KURL.h) to avoid platform-specific URL decoding |
| 190 // differences (e.g. KURLGoogle). |
| 180 return decodeEscapeSequences<URLEscapeSequence>(string, encoding); | 191 return decodeEscapeSequences<URLEscapeSequence>(string, encoding); |
| 181 } | 192 } |
| 182 | 193 |
| 183 static String fullyDecodeString(const String& string, | 194 static String fullyDecodeString(const String& string, |
| 184 const WTF::TextEncoding& encoding) { | 195 const WTF::TextEncoding& encoding) { |
| 185 size_t oldWorkingStringLength; | 196 size_t oldWorkingStringLength; |
| 186 String workingString = string; | 197 String workingString = string; |
| 187 do { | 198 do { |
| 188 oldWorkingStringLength = workingString.length(); | 199 oldWorkingStringLength = workingString.length(); |
| 189 workingString = decode16BitUnicodeEscapeSequences( | 200 workingString = decode16BitUnicodeEscapeSequences( |
| 190 decodeStandardURLEscapeSequences(workingString, encoding)); | 201 decodeStandardURLEscapeSequences(workingString, encoding)); |
| 191 } while (workingString.length() < oldWorkingStringLength); | 202 } while (workingString.length() < oldWorkingStringLength); |
| 192 workingString.replace('+', ' '); | 203 workingString.replace('+', ' '); |
| 193 return workingString; | 204 return workingString; |
| 194 } | 205 } |
| 195 | 206 |
| 196 static void truncateForSrcLikeAttribute(String& decodedSnippet) { | 207 static void truncateForSrcLikeAttribute(String& decodedSnippet) { |
| 197 // In HTTP URLs, characters following the first ?, #, or third slash may come
from | 208 // In HTTP URLs, characters following the first ?, #, or third slash may come |
| 198 // the page itself and can be merely ignored by an attacker's server when a re
mote | 209 // from the page itself and can be merely ignored by an attacker's server when |
| 199 // script or script-like resource is requested. In DATA URLS, the payload star
ts at | 210 // a remote script or script-like resource is requested. In DATA URLS, the |
| 200 // the first comma, and the the first /*, //, or <!-- may introduce a comment.
Also, | 211 // payload starts at the first comma, and the the first /*, //, or <!-- may |
| 201 // DATA URLs may use the same string literal tricks as with script content its
elf. | 212 // introduce a comment. |
| 202 // In either case, content following this may come from the page and may be ig
nored | 213 // |
| 203 // when the script is executed. Also, any of these characters may now be repre
sented | 214 // Also, DATA URLs may use the same string literal tricks as with script |
| 204 // by the (enlarged) set of html5 entities. | 215 // content itself. In either case, content following this may come from the |
| 205 // For simplicity, we don't differentiate based on URL scheme, and stop at the
first | 216 // page and may be ignored when the script is executed. Also, any of these |
| 206 // & (since it might be part of an entity for any of the subsequent punctuatio
n), the | 217 // characters may now be represented by the (enlarged) set of html5 entities. |
| 207 // first # or ?, the third slash, or the first slash, <, ', or " once a comma
is seen. | 218 // |
| 219 // For simplicity, we don't differentiate based on URL scheme, and stop at the |
| 220 // first & (since it might be part of an entity for any of the subsequent |
| 221 // punctuation), the first # or ?, the third slash, or the first slash, <, ', |
| 222 // or " once a comma is seen. |
| 208 int slashCount = 0; | 223 int slashCount = 0; |
| 209 bool commaSeen = false; | 224 bool commaSeen = false; |
| 210 for (size_t currentLength = 0; currentLength < decodedSnippet.length(); | 225 for (size_t currentLength = 0; currentLength < decodedSnippet.length(); |
| 211 ++currentLength) { | 226 ++currentLength) { |
| 212 UChar currentChar = decodedSnippet[currentLength]; | 227 UChar currentChar = decodedSnippet[currentLength]; |
| 213 if (currentChar == '&' || currentChar == '?' || currentChar == '#' || | 228 if (currentChar == '&' || currentChar == '?' || currentChar == '#' || |
| 214 ((currentChar == '/' || currentChar == '\\') && | 229 ((currentChar == '/' || currentChar == '\\') && |
| 215 (commaSeen || ++slashCount > 2)) || | 230 (commaSeen || ++slashCount > 2)) || |
| 216 (currentChar == '<' && commaSeen) || | 231 (currentChar == '<' && commaSeen) || |
| 217 (currentChar == '\'' && commaSeen) || | 232 (currentChar == '\'' && commaSeen) || |
| 218 (currentChar == '"' && commaSeen)) { | 233 (currentChar == '"' && commaSeen)) { |
| 219 decodedSnippet.truncate(currentLength); | 234 decodedSnippet.truncate(currentLength); |
| 220 return; | 235 return; |
| 221 } | 236 } |
| 222 if (currentChar == ',') | 237 if (currentChar == ',') |
| 223 commaSeen = true; | 238 commaSeen = true; |
| 224 } | 239 } |
| 225 } | 240 } |
| 226 | 241 |
| 227 static void truncateForScriptLikeAttribute(String& decodedSnippet) { | 242 static void truncateForScriptLikeAttribute(String& decodedSnippet) { |
| 228 // Beware of trailing characters which came from the page itself, not the | 243 // Beware of trailing characters which came from the page itself, not the |
| 229 // injected vector. Excluding the terminating character covers common cases | 244 // injected vector. Excluding the terminating character covers common cases |
| 230 // where the page immediately ends the attribute, but doesn't cover more | 245 // where the page immediately ends the attribute, but doesn't cover more |
| 231 // complex cases where there is other page data following the injection. | 246 // complex cases where there is other page data following the injection. |
| 247 // |
| 232 // Generally, these won't parse as javascript, so the injected vector | 248 // Generally, these won't parse as javascript, so the injected vector |
| 233 // typically excludes them from consideration via a single-line comment or | 249 // typically excludes them from consideration via a single-line comment or |
| 234 // by enclosing them in a string literal terminated later by the page's own | 250 // by enclosing them in a string literal terminated later by the page's own |
| 235 // closing punctuation. Since the snippet has not been parsed, the vector | 251 // closing punctuation. Since the snippet has not been parsed, the vector |
| 236 // may also try to introduce these via entities. As a result, we'd like to | 252 // may also try to introduce these via entities. As a result, we'd like to |
| 237 // stop before the first "//", the first <!--, the first entity, or the first | 253 // stop before the first "//", the first <!--, the first entity, or the first |
| 238 // quote not immediately following the first equals sign (taking whitespace | 254 // quote not immediately following the first equals sign (taking whitespace |
| 239 // into consideration). To keep things simpler, we don't try to distinguish | 255 // into consideration). |
| 240 // between entity-introducing amperands vs. other uses, nor do we bother to | 256 // |
| 241 // check for a second slash for a comment, nor do we bother to check for | 257 // To keep things simpler, we don't try to distinguish between |
| 242 // !-- following a less-than sign. We stop instead on any ampersand | 258 // entity-introducing amperands vs. other uses, nor do we bother to check for |
| 243 // slash, or less-than sign. | 259 // a second slash for a comment, nor do we bother to check for !-- following a |
| 260 // less-than sign. We stop instead on any ampersand slash, or less-than sign. |
| 244 size_t position = 0; | 261 size_t position = 0; |
| 245 if ((position = decodedSnippet.find("=")) != kNotFound && | 262 if ((position = decodedSnippet.find("=")) != kNotFound && |
| 246 (position = decodedSnippet.find(isNotHTMLSpace<UChar>, position + 1)) != | 263 (position = decodedSnippet.find(isNotHTMLSpace<UChar>, position + 1)) != |
| 247 kNotFound && | 264 kNotFound && |
| 248 (position = decodedSnippet.find( | 265 (position = decodedSnippet.find( |
| 249 isTerminatingCharacter, | 266 isTerminatingCharacter, |
| 250 isHTMLQuote(decodedSnippet[position]) ? position + 1 : position)) != | 267 isHTMLQuote(decodedSnippet[position]) ? position + 1 : position)) != |
| 251 kNotFound) { | 268 kNotFound) { |
| 252 decodedSnippet.truncate(position); | 269 decodedSnippet.truncate(position); |
| 253 } | 270 } |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 319 m_documentURL = document->url().copy(); | 336 m_documentURL = document->url().copy(); |
| 320 | 337 |
| 321 // In theory, the Document could have detached from the LocalFrame after the | 338 // In theory, the Document could have detached from the LocalFrame after the |
| 322 // XSSAuditor was constructed. | 339 // XSSAuditor was constructed. |
| 323 if (!document->frame()) { | 340 if (!document->frame()) { |
| 324 m_isEnabled = false; | 341 m_isEnabled = false; |
| 325 return; | 342 return; |
| 326 } | 343 } |
| 327 | 344 |
| 328 if (m_documentURL.isEmpty()) { | 345 if (m_documentURL.isEmpty()) { |
| 329 // The URL can be empty when opening a new browser window or calling window.
open(""). | 346 // The URL can be empty when opening a new browser window or calling |
| 347 // window.open(""). |
| 330 m_isEnabled = false; | 348 m_isEnabled = false; |
| 331 return; | 349 return; |
| 332 } | 350 } |
| 333 | 351 |
| 334 if (m_documentURL.protocolIsData()) { | 352 if (m_documentURL.protocolIsData()) { |
| 335 m_isEnabled = false; | 353 m_isEnabled = false; |
| 336 return; | 354 return; |
| 337 } | 355 } |
| 338 | 356 |
| 339 if (document->encoding().isValid()) | 357 if (document->encoding().isValid()) |
| (...skipping 165 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 505 | 523 |
| 506 if (m_state == FilteringTokens && m_scriptTagFoundInRequest) { | 524 if (m_state == FilteringTokens && m_scriptTagFoundInRequest) { |
| 507 String snippet = canonicalizedSnippetForJavaScript(request); | 525 String snippet = canonicalizedSnippetForJavaScript(request); |
| 508 if (isContainedInRequest(snippet)) | 526 if (isContainedInRequest(snippet)) |
| 509 m_state = SuppressingAdjacentCharacterTokens; | 527 m_state = SuppressingAdjacentCharacterTokens; |
| 510 else if (!snippet.isEmpty()) | 528 else if (!snippet.isEmpty()) |
| 511 m_state = PermittingAdjacentCharacterTokens; | 529 m_state = PermittingAdjacentCharacterTokens; |
| 512 } | 530 } |
| 513 if (m_state == SuppressingAdjacentCharacterTokens) { | 531 if (m_state == SuppressingAdjacentCharacterTokens) { |
| 514 request.token.eraseCharacters(); | 532 request.token.eraseCharacters(); |
| 515 request.token.appendToCharacter( | 533 // Technically, character tokens can't be empty. |
| 516 ' '); // Technically, character tokens can't be empty. | 534 request.token.appendToCharacter(' '); |
| 517 return true; | 535 return true; |
| 518 } | 536 } |
| 519 return false; | 537 return false; |
| 520 } | 538 } |
| 521 | 539 |
| 522 bool XSSAuditor::filterScriptToken(const FilterTokenRequest& request) { | 540 bool XSSAuditor::filterScriptToken(const FilterTokenRequest& request) { |
| 523 ASSERT(request.token.type() == HTMLToken::StartTag); | 541 ASSERT(request.token.type() == HTMLToken::StartTag); |
| 524 ASSERT(hasName(request.token, scriptTag)); | 542 ASSERT(hasName(request.token, scriptTag)); |
| 525 | 543 |
| 526 bool didBlockScript = false; | 544 bool didBlockScript = false; |
| (...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 655 AllowSameOriginHref); | 673 AllowSameOriginHref); |
| 656 } | 674 } |
| 657 | 675 |
| 658 bool XSSAuditor::eraseDangerousAttributesIfInjected( | 676 bool XSSAuditor::eraseDangerousAttributesIfInjected( |
| 659 const FilterTokenRequest& request) { | 677 const FilterTokenRequest& request) { |
| 660 bool didBlockScript = false; | 678 bool didBlockScript = false; |
| 661 for (size_t i = 0; i < request.token.attributes().size(); ++i) { | 679 for (size_t i = 0; i < request.token.attributes().size(); ++i) { |
| 662 bool eraseAttribute = false; | 680 bool eraseAttribute = false; |
| 663 bool valueContainsJavaScriptURL = false; | 681 bool valueContainsJavaScriptURL = false; |
| 664 const HTMLToken::Attribute& attribute = request.token.attributes().at(i); | 682 const HTMLToken::Attribute& attribute = request.token.attributes().at(i); |
| 665 // FIXME: Don't create a new String for every attribute.value in the documen
t. | 683 // FIXME: Don't create a new String for every attribute.value in the |
| 684 // document. |
| 666 if (isNameOfInlineEventHandler(attribute.nameAsVector())) { | 685 if (isNameOfInlineEventHandler(attribute.nameAsVector())) { |
| 667 eraseAttribute = isContainedInRequest( | 686 eraseAttribute = isContainedInRequest( |
| 668 canonicalize(snippetFromAttribute(request, attribute), | 687 canonicalize(snippetFromAttribute(request, attribute), |
| 669 ScriptLikeAttributeTruncation)); | 688 ScriptLikeAttributeTruncation)); |
| 670 } else if (isSemicolonSeparatedAttribute(attribute)) { | 689 } else if (isSemicolonSeparatedAttribute(attribute)) { |
| 671 String subValue = | 690 String subValue = |
| 672 semicolonSeparatedValueContainingJavaScriptURL(attribute.value()); | 691 semicolonSeparatedValueContainingJavaScriptURL(attribute.value()); |
| 673 if (!subValue.isEmpty()) { | 692 if (!subValue.isEmpty()) { |
| 674 valueContainsJavaScriptURL = true; | 693 valueContainsJavaScriptURL = true; |
| 675 eraseAttribute = | 694 eraseAttribute = |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 722 | 741 |
| 723 request.token.eraseValueOfAttribute(indexOfAttribute); | 742 request.token.eraseValueOfAttribute(indexOfAttribute); |
| 724 if (!replacementValue.isEmpty()) | 743 if (!replacementValue.isEmpty()) |
| 725 request.token.appendToAttributeValue(indexOfAttribute, replacementValue); | 744 request.token.appendToAttributeValue(indexOfAttribute, replacementValue); |
| 726 | 745 |
| 727 return true; | 746 return true; |
| 728 } | 747 } |
| 729 | 748 |
| 730 String XSSAuditor::canonicalizedSnippetForTagName( | 749 String XSSAuditor::canonicalizedSnippetForTagName( |
| 731 const FilterTokenRequest& request) { | 750 const FilterTokenRequest& request) { |
| 732 // Grab a fixed number of characters equal to the length of the token's name p
lus one (to account for the "<"). | 751 // Grab a fixed number of characters equal to the length of the token's name |
| 752 // plus one (to account for the "<"). |
| 733 return canonicalize(request.sourceTracker.sourceForToken(request.token) | 753 return canonicalize(request.sourceTracker.sourceForToken(request.token) |
| 734 .substring(0, request.token.name().size() + 1), | 754 .substring(0, request.token.name().size() + 1), |
| 735 NoTruncation); | 755 NoTruncation); |
| 736 } | 756 } |
| 737 | 757 |
| 738 String XSSAuditor::nameFromAttribute(const FilterTokenRequest& request, | 758 String XSSAuditor::nameFromAttribute(const FilterTokenRequest& request, |
| 739 const HTMLToken::Attribute& attribute) { | 759 const HTMLToken::Attribute& attribute) { |
| 740 // The range inlcudes the character which terminates the name. So, | 760 // The range inlcudes the character which terminates the name. So, |
| 741 // for an input of |name="value"|, the snippet is |name=|. | 761 // for an input of |name="value"|, the snippet is |name=|. |
| 742 int start = attribute.nameRange().start - request.token.startIndex(); | 762 int start = attribute.nameRange().start - request.token.startIndex(); |
| (...skipping 12 matching lines...) Expand all Loading... |
| 755 int end = attribute.valueRange().end - request.token.startIndex(); | 775 int end = attribute.valueRange().end - request.token.startIndex(); |
| 756 return request.sourceTracker.sourceForToken(request.token) | 776 return request.sourceTracker.sourceForToken(request.token) |
| 757 .substring(start, end - start); | 777 .substring(start, end - start); |
| 758 } | 778 } |
| 759 | 779 |
| 760 String XSSAuditor::canonicalize(String snippet, TruncationKind treatment) { | 780 String XSSAuditor::canonicalize(String snippet, TruncationKind treatment) { |
| 761 String decodedSnippet = fullyDecodeString(snippet, m_encoding); | 781 String decodedSnippet = fullyDecodeString(snippet, m_encoding); |
| 762 | 782 |
| 763 if (treatment != NoTruncation) { | 783 if (treatment != NoTruncation) { |
| 764 if (decodedSnippet.length() > kMaximumFragmentLengthTarget) { | 784 if (decodedSnippet.length() > kMaximumFragmentLengthTarget) { |
| 765 // Let the page influence the stopping point to avoid disclosing leading f
ragments. | 785 // Let the page influence the stopping point to avoid disclosing leading |
| 766 // Stop when we hit whitespace, since that is unlikely to be part a leadin
g fragment. | 786 // fragments. Stop when we hit whitespace, since that is unlikely to be |
| 787 // part a leading fragment. |
| 767 size_t position = kMaximumFragmentLengthTarget; | 788 size_t position = kMaximumFragmentLengthTarget; |
| 768 while (position < decodedSnippet.length() && | 789 while (position < decodedSnippet.length() && |
| 769 !isHTMLSpace(decodedSnippet[position])) | 790 !isHTMLSpace(decodedSnippet[position])) |
| 770 ++position; | 791 ++position; |
| 771 decodedSnippet.truncate(position); | 792 decodedSnippet.truncate(position); |
| 772 } | 793 } |
| 773 if (treatment == SrcLikeAttributeTruncation) | 794 if (treatment == SrcLikeAttributeTruncation) |
| 774 truncateForSrcLikeAttribute(decodedSnippet); | 795 truncateForSrcLikeAttribute(decodedSnippet); |
| 775 else if (treatment == ScriptLikeAttributeTruncation) | 796 else if (treatment == ScriptLikeAttributeTruncation) |
| 776 truncateForScriptLikeAttribute(decodedSnippet); | 797 truncateForScriptLikeAttribute(decodedSnippet); |
| 777 } | 798 } |
| 778 | 799 |
| 779 return decodedSnippet.removeCharacters(&isNonCanonicalCharacter); | 800 return decodedSnippet.removeCharacters(&isNonCanonicalCharacter); |
| 780 } | 801 } |
| 781 | 802 |
| 782 String XSSAuditor::canonicalizedSnippetForJavaScript( | 803 String XSSAuditor::canonicalizedSnippetForJavaScript( |
| 783 const FilterTokenRequest& request) { | 804 const FilterTokenRequest& request) { |
| 784 String string = request.sourceTracker.sourceForToken(request.token); | 805 String string = request.sourceTracker.sourceForToken(request.token); |
| 785 size_t startPosition = 0; | 806 size_t startPosition = 0; |
| 786 size_t endPosition = string.length(); | 807 size_t endPosition = string.length(); |
| 787 size_t foundPosition = kNotFound; | 808 size_t foundPosition = kNotFound; |
| 788 size_t lastNonSpacePosition = kNotFound; | 809 size_t lastNonSpacePosition = kNotFound; |
| 789 | 810 |
| 790 // Skip over initial comments to find start of code. | 811 // Skip over initial comments to find start of code. |
| 791 while (startPosition < endPosition) { | 812 while (startPosition < endPosition) { |
| 792 while (startPosition < endPosition && | 813 while (startPosition < endPosition && |
| 793 isHTMLSpace<UChar>(string[startPosition])) | 814 isHTMLSpace<UChar>(string[startPosition])) |
| 794 startPosition++; | 815 startPosition++; |
| 795 | 816 |
| 796 // Under SVG/XML rules, only HTML comment syntax matters and the parser retu
rns | 817 // Under SVG/XML rules, only HTML comment syntax matters and the parser |
| 797 // these as a separate comment tokens. Having consumed whitespace, we need n
ot look | 818 // returns these as a separate comment tokens. Having consumed whitespace, |
| 798 // further for these. | 819 // we need not look further for these. |
| 799 if (request.shouldAllowCDATA) | 820 if (request.shouldAllowCDATA) |
| 800 break; | 821 break; |
| 801 | 822 |
| 802 // Under HTML rules, both the HTML and JS comment synatx matters, and the HT
ML | 823 // Under HTML rules, both the HTML and JS comment synatx matters, and the |
| 803 // comment ends at the end of the line, not with -->. | 824 // HTML comment ends at the end of the line, not with -->. |
| 804 if (startsHTMLCommentAt(string, startPosition) || | 825 if (startsHTMLCommentAt(string, startPosition) || |
| 805 startsSingleLineCommentAt(string, startPosition)) { | 826 startsSingleLineCommentAt(string, startPosition)) { |
| 806 while (startPosition < endPosition && !isJSNewline(string[startPosition])) | 827 while (startPosition < endPosition && !isJSNewline(string[startPosition])) |
| 807 startPosition++; | 828 startPosition++; |
| 808 } else if (startsMultiLineCommentAt(string, startPosition)) { | 829 } else if (startsMultiLineCommentAt(string, startPosition)) { |
| 809 if (startPosition + 2 < endPosition && | 830 if (startPosition + 2 < endPosition && |
| 810 (foundPosition = string.find("*/", startPosition + 2)) != kNotFound) | 831 (foundPosition = string.find("*/", startPosition + 2)) != kNotFound) |
| 811 startPosition = foundPosition + 2; | 832 startPosition = foundPosition + 2; |
| 812 else | 833 else |
| 813 startPosition = endPosition; | 834 startPosition = endPosition; |
| 814 } else | 835 } else |
| 815 break; | 836 break; |
| 816 } | 837 } |
| 817 | 838 |
| 818 String result; | 839 String result; |
| 819 while (startPosition < endPosition && !result.length()) { | 840 while (startPosition < endPosition && !result.length()) { |
| 820 // Stop at next comment (using the same rules as above for SVG/XML vs HTML),
when we encounter a comma, | 841 // Stop at next comment (using the same rules as above for SVG/XML vs HTML), |
| 821 // when we encoutner a backtick, when we hit an opening <script> tag, or whe
n we exceed the maximum length | 842 // when we encounter a comma, when we encoutner a backtick, when we hit an |
| 822 // target. The comma rule covers a common parameter concatenation case perfo
rmed by some web servers. The | 843 // opening <script> tag, or when we exceed the maximum length target. The |
| 823 // backtick rule covers the ECMA6 multi-line template string feature. | 844 // comma rule covers a common parameter concatenation case performed by some |
| 845 // web servers. The backtick rule covers the ECMA6 multi-line template |
| 846 // string feature. |
| 824 lastNonSpacePosition = kNotFound; | 847 lastNonSpacePosition = kNotFound; |
| 825 for (foundPosition = startPosition; foundPosition < endPosition; | 848 for (foundPosition = startPosition; foundPosition < endPosition; |
| 826 foundPosition++) { | 849 foundPosition++) { |
| 827 if (!request.shouldAllowCDATA) { | 850 if (!request.shouldAllowCDATA) { |
| 828 if (startsSingleLineCommentAt(string, foundPosition) || | 851 if (startsSingleLineCommentAt(string, foundPosition) || |
| 829 startsMultiLineCommentAt(string, foundPosition) || | 852 startsMultiLineCommentAt(string, foundPosition) || |
| 830 startsHTMLCommentAt(string, foundPosition)) { | 853 startsHTMLCommentAt(string, foundPosition)) { |
| 831 break; | 854 break; |
| 832 } | 855 } |
| 833 } | 856 } |
| 834 if (string[foundPosition] == ',' || string[foundPosition] == '`') | 857 if (string[foundPosition] == ',' || string[foundPosition] == '`') |
| 835 break; | 858 break; |
| 836 | 859 |
| 837 if (lastNonSpacePosition != kNotFound && | 860 if (lastNonSpacePosition != kNotFound && |
| 838 startsOpeningScriptTagAt(string, foundPosition)) { | 861 startsOpeningScriptTagAt(string, foundPosition)) { |
| 839 foundPosition = lastNonSpacePosition + 1; | 862 foundPosition = lastNonSpacePosition + 1; |
| 840 break; | 863 break; |
| 841 } | 864 } |
| 842 if (foundPosition > startPosition + kMaximumFragmentLengthTarget) { | 865 if (foundPosition > startPosition + kMaximumFragmentLengthTarget) { |
| 843 // After hitting the length target, we can only stop at a point where we
know we are | 866 // After hitting the length target, we can only stop at a point where we |
| 844 // not in the middle of a %-escape sequence. For the sake of simplicity,
approximate | 867 // know we are not in the middle of a %-escape sequence. For the sake of |
| 845 // not stopping inside a (possibly multiply encoded) %-escape sequence b
y breaking on | 868 // simplicity, approximate not stopping inside a (possibly multiply |
| 846 // whitespace only. We should have enough text in these cases to avoid f
alse positives. | 869 // encoded) %-escape sequence by breaking on whitespace only. We should |
| 870 // have enough text in these cases to avoid false positives. |
| 847 if (isHTMLSpace<UChar>(string[foundPosition])) | 871 if (isHTMLSpace<UChar>(string[foundPosition])) |
| 848 break; | 872 break; |
| 849 } | 873 } |
| 850 if (!isHTMLSpace<UChar>(string[foundPosition])) | 874 if (!isHTMLSpace<UChar>(string[foundPosition])) |
| 851 lastNonSpacePosition = foundPosition; | 875 lastNonSpacePosition = foundPosition; |
| 852 } | 876 } |
| 853 result = canonicalize( | 877 result = canonicalize( |
| 854 string.substring(startPosition, foundPosition - startPosition), | 878 string.substring(startPosition, foundPosition - startPosition), |
| 855 NoTruncation); | 879 NoTruncation); |
| 856 startPosition = foundPosition + 1; | 880 startPosition = foundPosition + 1; |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 893 } | 917 } |
| 894 | 918 |
| 895 bool XSSAuditor::isSafeToSendToAnotherThread() const { | 919 bool XSSAuditor::isSafeToSendToAnotherThread() const { |
| 896 return m_documentURL.isSafeToSendToAnotherThread() && | 920 return m_documentURL.isSafeToSendToAnotherThread() && |
| 897 m_decodedURL.isSafeToSendToAnotherThread() && | 921 m_decodedURL.isSafeToSendToAnotherThread() && |
| 898 m_decodedHTTPBody.isSafeToSendToAnotherThread() && | 922 m_decodedHTTPBody.isSafeToSendToAnotherThread() && |
| 899 m_httpBodyAsString.isSafeToSendToAnotherThread(); | 923 m_httpBodyAsString.isSafeToSendToAnotherThread(); |
| 900 } | 924 } |
| 901 | 925 |
| 902 } // namespace blink | 926 } // namespace blink |
| OLD | NEW |