Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(570)

Side by Side Diff: Source/core/html/parser/XSSAuditor.cpp

Issue 338193002: Refactor XSS Auditor string operations. (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: nameFromAttribute() not needed until next CL lands. Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « Source/core/html/parser/XSSAuditor.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
64 // Note, we don't remove backslashes like PHP stripslashes(), which among ot her things converts "\\0" to the \0 character. 64 // Note, we don't remove backslashes like PHP stripslashes(), which among ot her things converts "\\0" to the \0 character.
65 // Instead, we remove backslashes and zeros (since the string "\\0" =(remove backslashes)=> "0"). However, this has the 65 // Instead, we remove backslashes and zeros (since the string "\\0" =(remove backslashes)=> "0"). However, this has the
66 // adverse effect that we remove any legitimate zeros from a string. 66 // adverse effect that we remove any legitimate zeros from a string.
67 // We also remove forward-slash, because it is common for some servers to co llapse successive path components, eg, 67 // We also remove forward-slash, because it is common for some servers to co llapse successive path components, eg,
68 // a//b becomes a/b. 68 // a//b becomes a/b.
69 // 69 //
70 // For instance: new String("http://localhost:8000") => new String("http:loc alhost:8"). 70 // For instance: new String("http://localhost:8000") => new String("http:loc alhost:8").
71 return (c == '\\' || c == '0' || c == '\0' || c == '/' || c >= 127); 71 return (c == '\\' || c == '0' || c == '\0' || c == '/' || c >= 127);
72 } 72 }
73 73
74 static String canonicalize(const String& string)
75 {
76 return string.removeCharacters(&isNonCanonicalCharacter);
77 }
78
79 static bool isRequiredForInjection(UChar c) 74 static bool isRequiredForInjection(UChar c)
80 { 75 {
81 return (c == '\'' || c == '"' || c == '<' || c == '>'); 76 return (c == '\'' || c == '"' || c == '<' || c == '>');
82 } 77 }
83 78
84 static bool isTerminatingCharacter(UChar c) 79 static bool isTerminatingCharacter(UChar c)
85 { 80 {
86 return (c == '&' || c == '/' || c == '"' || c == '\'' || c == '<' || c == '> ' || c == ','); 81 return (c == '&' || c == '/' || c == '"' || c == '\'' || c == '<' || c == '> ' || c == ',');
87 } 82 }
88 83
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
181 size_t oldWorkingStringLength; 176 size_t oldWorkingStringLength;
182 String workingString = string; 177 String workingString = string;
183 do { 178 do {
184 oldWorkingStringLength = workingString.length(); 179 oldWorkingStringLength = workingString.length();
185 workingString = decode16BitUnicodeEscapeSequences(decodeStandardURLEscap eSequences(workingString, encoding)); 180 workingString = decode16BitUnicodeEscapeSequences(decodeStandardURLEscap eSequences(workingString, encoding));
186 } while (workingString.length() < oldWorkingStringLength); 181 } while (workingString.length() < oldWorkingStringLength);
187 workingString.replace('+', ' '); 182 workingString.replace('+', ' ');
188 return workingString; 183 return workingString;
189 } 184 }
190 185
186 static void truncateForSrcLikeAttribute(String& decodedSnippet)
187 {
188 // In HTTP URLs, characters following the first ?, #, or third slash may com e from
189 // the page itself and can be merely ignored by an attacker's server when a remote
190 // script or script-like resource is requested. In DATA URLS, the payload st arts at
191 // the first comma, and the the first /*, //, or <!-- may introduce a commen t. Characters
192 // following this may come from the page itself and may be ignored when the script is
193 // executed. For simplicity, we don't differentiate based on URL scheme, and stop at
194 // the first # or ?, the third slash, or the first slash or < once a comma i s seen.
195 int slashCount = 0;
196 bool commaSeen = false;
197 for (size_t currentLength = 0; currentLength < decodedSnippet.length(); ++cu rrentLength) {
198 UChar currentChar = decodedSnippet[currentLength];
199 if (currentChar == '?'
200 || currentChar == '#'
201 || ((currentChar == '/' || currentChar == '\\') && (commaSeen || ++s lashCount > 2))
202 || (currentChar == '<' && commaSeen)) {
203 decodedSnippet.truncate(currentLength);
204 return;
205 }
206 if (currentChar == ',')
207 commaSeen = true;
208 }
209 }
210
211 static void truncateForScriptLikeAttribute(String& decodedSnippet)
212 {
213 // Beware of trailing characters which came from the page itself, not the
214 // injected vector. Excluding the terminating character covers common cases
215 // where the page immediately ends the attribute, but doesn't cover more
216 // complex cases where there is other page data following the injection.
217 // Generally, these won't parse as javascript, so the injected vector
218 // typically excludes them from consideration via a single-line comment or
219 // by enclosing them in a string literal terminated later by the page's own
220 // closing punctuation. Since the snippet has not been parsed, the vector
221 // may also try to introduce these via entities. As a result, we'd like to
222 // stop before the first "//", the first <!--, the first entity, or the firs t
223 // quote not immediately following the first equals sign (taking whitespace
224 // into consideration). To keep things simpler, we don't try to distinguish
225 // between entity-introducing amperands vs. other uses, nor do we bother to
226 // check for a second slash for a comment, nor do we bother to check for
227 // !-- following a less-than sign. We stop instead on any ampersand
228 // slash, or less-than sign.
229 size_t position = 0;
230 if ((position = decodedSnippet.find("=")) != kNotFound
231 && (position = decodedSnippet.find(isNotHTMLSpace<UChar>, position + 1)) != kNotFound
232 && (position = decodedSnippet.find(isTerminatingCharacter, isHTMLQuote(d ecodedSnippet[position]) ? position + 1 : position)) != kNotFound) {
233 decodedSnippet.truncate(position);
234 }
235 }
236
191 static ReflectedXSSDisposition combineXSSProtectionHeaderAndCSP(ReflectedXSSDisp osition xssProtection, ReflectedXSSDisposition reflectedXSS) 237 static ReflectedXSSDisposition combineXSSProtectionHeaderAndCSP(ReflectedXSSDisp osition xssProtection, ReflectedXSSDisposition reflectedXSS)
192 { 238 {
193 ReflectedXSSDisposition result = std::max(xssProtection, reflectedXSS); 239 ReflectedXSSDisposition result = std::max(xssProtection, reflectedXSS);
194 240
195 if (result == ReflectedXSSInvalid || result == FilterReflectedXSS || result == ReflectedXSSUnset) 241 if (result == ReflectedXSSInvalid || result == FilterReflectedXSS || result == ReflectedXSSUnset)
196 return FilterReflectedXSS; 242 return FilterReflectedXSS;
197 243
198 return result; 244 return result;
199 } 245 }
200 246
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after
315 void XSSAuditor::setEncoding(const WTF::TextEncoding& encoding) 361 void XSSAuditor::setEncoding(const WTF::TextEncoding& encoding)
316 { 362 {
317 const size_t miniumLengthForSuffixTree = 512; // FIXME: Tune this parameter. 363 const size_t miniumLengthForSuffixTree = 512; // FIXME: Tune this parameter.
318 const int suffixTreeDepth = 5; 364 const int suffixTreeDepth = 5;
319 365
320 if (!encoding.isValid()) 366 if (!encoding.isValid())
321 return; 367 return;
322 368
323 m_encoding = encoding; 369 m_encoding = encoding;
324 370
325 m_decodedURL = canonicalize(fullyDecodeString(m_documentURL.string(), m_enco ding)); 371 m_decodedURL = canonicalize(m_documentURL.string(), NoTruncation);
326 if (m_decodedURL.find(isRequiredForInjection) == kNotFound) 372 if (m_decodedURL.find(isRequiredForInjection) == kNotFound)
327 m_decodedURL = String(); 373 m_decodedURL = String();
328 374
329 if (!m_httpBodyAsString.isEmpty()) { 375 if (!m_httpBodyAsString.isEmpty()) {
330 m_decodedHTTPBody = canonicalize(fullyDecodeString(m_httpBodyAsString, m _encoding)); 376 m_decodedHTTPBody = canonicalize(m_httpBodyAsString, NoTruncation);
331 m_httpBodyAsString = String(); 377 m_httpBodyAsString = String();
332 if (m_decodedHTTPBody.find(isRequiredForInjection) == kNotFound) 378 if (m_decodedHTTPBody.find(isRequiredForInjection) == kNotFound)
333 m_decodedHTTPBody = String(); 379 m_decodedHTTPBody = String();
334 if (m_decodedHTTPBody.length() >= miniumLengthForSuffixTree) 380 if (m_decodedHTTPBody.length() >= miniumLengthForSuffixTree)
335 m_decodedHTTPBodySuffixTree = adoptPtr(new SuffixTree<ASCIICodeb ook>(m_decodedHTTPBody, suffixTreeDepth)); 381 m_decodedHTTPBodySuffixTree = adoptPtr(new SuffixTree<ASCIICodeb ook>(m_decodedHTTPBody, suffixTreeDepth));
336 } 382 }
337 383
338 if (m_decodedURL.isEmpty() && m_decodedHTTPBody.isEmpty()) 384 if (m_decodedURL.isEmpty() && m_decodedHTTPBody.isEmpty())
339 m_isEnabled = false; 385 m_isEnabled = false;
340 } 386 }
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
407 } 453 }
408 454
409 bool XSSAuditor::filterCharacterToken(const FilterTokenRequest& request) 455 bool XSSAuditor::filterCharacterToken(const FilterTokenRequest& request)
410 { 456 {
411 ASSERT(m_scriptTagNestingLevel); 457 ASSERT(m_scriptTagNestingLevel);
412 ASSERT(m_state != Uninitialized); 458 ASSERT(m_state != Uninitialized);
413 if (m_state == PermittingAdjacentCharacterTokens) 459 if (m_state == PermittingAdjacentCharacterTokens)
414 return false; 460 return false;
415 461
416 if ((m_state == SuppressingAdjacentCharacterTokens) 462 if ((m_state == SuppressingAdjacentCharacterTokens)
417 || (m_scriptTagFoundInRequest && isContainedInRequest(decodedSnippetForJ avaScript(request)))) { 463 || (m_scriptTagFoundInRequest && isContainedInRequest(canonicalizedSnipp etForJavaScript(request)))) {
418 request.token.eraseCharacters(); 464 request.token.eraseCharacters();
419 request.token.appendToCharacter(' '); // Technically, character tokens c an't be empty. 465 request.token.appendToCharacter(' '); // Technically, character tokens c an't be empty.
420 m_state = SuppressingAdjacentCharacterTokens; 466 m_state = SuppressingAdjacentCharacterTokens;
421 return true; 467 return true;
422 } 468 }
423 469
424 m_state = PermittingAdjacentCharacterTokens; 470 m_state = PermittingAdjacentCharacterTokens;
425 return false; 471 return false;
426 } 472 }
427 473
428 bool XSSAuditor::filterScriptToken(const FilterTokenRequest& request) 474 bool XSSAuditor::filterScriptToken(const FilterTokenRequest& request)
429 { 475 {
430 ASSERT(request.token.type() == HTMLToken::StartTag); 476 ASSERT(request.token.type() == HTMLToken::StartTag);
431 ASSERT(hasName(request.token, scriptTag)); 477 ASSERT(hasName(request.token, scriptTag));
432 478
433 bool didBlockScript = false; 479 bool didBlockScript = false;
434 m_scriptTagFoundInRequest = isContainedInRequest(decodedSnippetForName(reque st)); 480 m_scriptTagFoundInRequest = isContainedInRequest(canonicalizedSnippetForTagN ame(request));
435 if (m_scriptTagFoundInRequest) { 481 if (m_scriptTagFoundInRequest) {
436 didBlockScript |= eraseAttributeIfInjected(request, srcAttr, blankURL(). string(), SrcLikeAttribute); 482 didBlockScript |= eraseAttributeIfInjected(request, srcAttr, blankURL(). string(), SrcLikeAttributeTruncation);
437 didBlockScript |= eraseAttributeIfInjected(request, XLinkNames::hrefAttr , blankURL().string(), SrcLikeAttribute); 483 didBlockScript |= eraseAttributeIfInjected(request, XLinkNames::hrefAttr , blankURL().string(), SrcLikeAttributeTruncation);
438 } 484 }
439 return didBlockScript; 485 return didBlockScript;
440 } 486 }
441 487
442 bool XSSAuditor::filterObjectToken(const FilterTokenRequest& request) 488 bool XSSAuditor::filterObjectToken(const FilterTokenRequest& request)
443 { 489 {
444 ASSERT(request.token.type() == HTMLToken::StartTag); 490 ASSERT(request.token.type() == HTMLToken::StartTag);
445 ASSERT(hasName(request.token, objectTag)); 491 ASSERT(hasName(request.token, objectTag));
446 492
447 bool didBlockScript = false; 493 bool didBlockScript = false;
448 if (isContainedInRequest(decodedSnippetForName(request))) { 494 if (isContainedInRequest(canonicalizedSnippetForTagName(request))) {
449 didBlockScript |= eraseAttributeIfInjected(request, dataAttr, blankURL() .string(), SrcLikeAttribute); 495 didBlockScript |= eraseAttributeIfInjected(request, dataAttr, blankURL() .string(), SrcLikeAttributeTruncation);
450 didBlockScript |= eraseAttributeIfInjected(request, typeAttr); 496 didBlockScript |= eraseAttributeIfInjected(request, typeAttr);
451 didBlockScript |= eraseAttributeIfInjected(request, classidAttr); 497 didBlockScript |= eraseAttributeIfInjected(request, classidAttr);
452 } 498 }
453 return didBlockScript; 499 return didBlockScript;
454 } 500 }
455 501
456 bool XSSAuditor::filterParamToken(const FilterTokenRequest& request) 502 bool XSSAuditor::filterParamToken(const FilterTokenRequest& request)
457 { 503 {
458 ASSERT(request.token.type() == HTMLToken::StartTag); 504 ASSERT(request.token.type() == HTMLToken::StartTag);
459 ASSERT(hasName(request.token, paramTag)); 505 ASSERT(hasName(request.token, paramTag));
460 506
461 size_t indexOfNameAttribute; 507 size_t indexOfNameAttribute;
462 if (!findAttributeWithName(request.token, nameAttr, indexOfNameAttribute)) 508 if (!findAttributeWithName(request.token, nameAttr, indexOfNameAttribute))
463 return false; 509 return false;
464 510
465 const HTMLToken::Attribute& nameAttribute = request.token.attributes().at(in dexOfNameAttribute); 511 const HTMLToken::Attribute& nameAttribute = request.token.attributes().at(in dexOfNameAttribute);
466 if (!HTMLParamElement::isURLParameter(String(nameAttribute.value))) 512 if (!HTMLParamElement::isURLParameter(String(nameAttribute.value)))
467 return false; 513 return false;
468 514
469 return eraseAttributeIfInjected(request, valueAttr, blankURL().string(), Src LikeAttribute); 515 return eraseAttributeIfInjected(request, valueAttr, blankURL().string(), Src LikeAttributeTruncation);
470 } 516 }
471 517
472 bool XSSAuditor::filterEmbedToken(const FilterTokenRequest& request) 518 bool XSSAuditor::filterEmbedToken(const FilterTokenRequest& request)
473 { 519 {
474 ASSERT(request.token.type() == HTMLToken::StartTag); 520 ASSERT(request.token.type() == HTMLToken::StartTag);
475 ASSERT(hasName(request.token, embedTag)); 521 ASSERT(hasName(request.token, embedTag));
476 522
477 bool didBlockScript = false; 523 bool didBlockScript = false;
478 if (isContainedInRequest(decodedSnippetForName(request))) { 524 if (isContainedInRequest(canonicalizedSnippetForTagName(request))) {
479 didBlockScript |= eraseAttributeIfInjected(request, codeAttr, String(), SrcLikeAttribute); 525 didBlockScript |= eraseAttributeIfInjected(request, codeAttr, String(), SrcLikeAttributeTruncation);
480 didBlockScript |= eraseAttributeIfInjected(request, srcAttr, blankURL(). string(), SrcLikeAttribute); 526 didBlockScript |= eraseAttributeIfInjected(request, srcAttr, blankURL(). string(), SrcLikeAttributeTruncation);
481 didBlockScript |= eraseAttributeIfInjected(request, typeAttr); 527 didBlockScript |= eraseAttributeIfInjected(request, typeAttr);
482 } 528 }
483 return didBlockScript; 529 return didBlockScript;
484 } 530 }
485 531
486 bool XSSAuditor::filterAppletToken(const FilterTokenRequest& request) 532 bool XSSAuditor::filterAppletToken(const FilterTokenRequest& request)
487 { 533 {
488 ASSERT(request.token.type() == HTMLToken::StartTag); 534 ASSERT(request.token.type() == HTMLToken::StartTag);
489 ASSERT(hasName(request.token, appletTag)); 535 ASSERT(hasName(request.token, appletTag));
490 536
491 bool didBlockScript = false; 537 bool didBlockScript = false;
492 if (isContainedInRequest(decodedSnippetForName(request))) { 538 if (isContainedInRequest(canonicalizedSnippetForTagName(request))) {
493 didBlockScript |= eraseAttributeIfInjected(request, codeAttr, String(), SrcLikeAttribute); 539 didBlockScript |= eraseAttributeIfInjected(request, codeAttr, String(), SrcLikeAttributeTruncation);
494 didBlockScript |= eraseAttributeIfInjected(request, objectAttr); 540 didBlockScript |= eraseAttributeIfInjected(request, objectAttr);
495 } 541 }
496 return didBlockScript; 542 return didBlockScript;
497 } 543 }
498 544
499 bool XSSAuditor::filterFrameToken(const FilterTokenRequest& request) 545 bool XSSAuditor::filterFrameToken(const FilterTokenRequest& request)
500 { 546 {
501 ASSERT(request.token.type() == HTMLToken::StartTag); 547 ASSERT(request.token.type() == HTMLToken::StartTag);
502 ASSERT(hasName(request.token, iframeTag) || hasName(request.token, frameTag) ); 548 ASSERT(hasName(request.token, iframeTag) || hasName(request.token, frameTag) );
503 549
504 bool didBlockScript = eraseAttributeIfInjected(request, srcdocAttr, String() , ScriptLikeAttribute); 550 bool didBlockScript = eraseAttributeIfInjected(request, srcdocAttr, String() , ScriptLikeAttributeTruncation);
505 if (isContainedInRequest(decodedSnippetForName(request))) 551 if (isContainedInRequest(canonicalizedSnippetForTagName(request)))
506 didBlockScript |= eraseAttributeIfInjected(request, srcAttr, String(), S rcLikeAttribute); 552 didBlockScript |= eraseAttributeIfInjected(request, srcAttr, String(), S rcLikeAttributeTruncation);
507 553
508 return didBlockScript; 554 return didBlockScript;
509 } 555 }
510 556
511 bool XSSAuditor::filterMetaToken(const FilterTokenRequest& request) 557 bool XSSAuditor::filterMetaToken(const FilterTokenRequest& request)
512 { 558 {
513 ASSERT(request.token.type() == HTMLToken::StartTag); 559 ASSERT(request.token.type() == HTMLToken::StartTag);
514 ASSERT(hasName(request.token, metaTag)); 560 ASSERT(hasName(request.token, metaTag));
515 561
516 return eraseAttributeIfInjected(request, http_equivAttr); 562 return eraseAttributeIfInjected(request, http_equivAttr);
(...skipping 13 matching lines...) Expand all
530 ASSERT(hasName(request.token, formTag)); 576 ASSERT(hasName(request.token, formTag));
531 577
532 return eraseAttributeIfInjected(request, actionAttr, kURLWithUniqueOrigin); 578 return eraseAttributeIfInjected(request, actionAttr, kURLWithUniqueOrigin);
533 } 579 }
534 580
535 bool XSSAuditor::filterInputToken(const FilterTokenRequest& request) 581 bool XSSAuditor::filterInputToken(const FilterTokenRequest& request)
536 { 582 {
537 ASSERT(request.token.type() == HTMLToken::StartTag); 583 ASSERT(request.token.type() == HTMLToken::StartTag);
538 ASSERT(hasName(request.token, inputTag)); 584 ASSERT(hasName(request.token, inputTag));
539 585
540 return eraseAttributeIfInjected(request, formactionAttr, kURLWithUniqueOrigi n, SrcLikeAttribute); 586 return eraseAttributeIfInjected(request, formactionAttr, kURLWithUniqueOrigi n, SrcLikeAttributeTruncation);
541 } 587 }
542 588
543 bool XSSAuditor::filterButtonToken(const FilterTokenRequest& request) 589 bool XSSAuditor::filterButtonToken(const FilterTokenRequest& request)
544 { 590 {
545 ASSERT(request.token.type() == HTMLToken::StartTag); 591 ASSERT(request.token.type() == HTMLToken::StartTag);
546 ASSERT(hasName(request.token, buttonTag)); 592 ASSERT(hasName(request.token, buttonTag));
547 593
548 return eraseAttributeIfInjected(request, formactionAttr, kURLWithUniqueOrigi n, SrcLikeAttribute); 594 return eraseAttributeIfInjected(request, formactionAttr, kURLWithUniqueOrigi n, SrcLikeAttributeTruncation);
549 } 595 }
550 596
551 bool XSSAuditor::eraseDangerousAttributesIfInjected(const FilterTokenRequest& re quest) 597 bool XSSAuditor::eraseDangerousAttributesIfInjected(const FilterTokenRequest& re quest)
552 { 598 {
553 DEFINE_STATIC_LOCAL(String, safeJavaScriptURL, ("javascript:void(0)")); 599 DEFINE_STATIC_LOCAL(String, safeJavaScriptURL, ("javascript:void(0)"));
554 600
555 bool didBlockScript = false; 601 bool didBlockScript = false;
556 for (size_t i = 0; i < request.token.attributes().size(); ++i) { 602 for (size_t i = 0; i < request.token.attributes().size(); ++i) {
557 const HTMLToken::Attribute& attribute = request.token.attributes().at(i) ; 603 const HTMLToken::Attribute& attribute = request.token.attributes().at(i) ;
558 bool isInlineEventHandler = isNameOfInlineEventHandler(attribute.name); 604 bool isInlineEventHandler = isNameOfInlineEventHandler(attribute.name);
559 // FIXME: It would be better if we didn't create a new String for every attribute in the document. 605 // FIXME: It would be better if we didn't create a new String for every attribute in the document.
560 String strippedValue = stripLeadingAndTrailingHTMLSpaces(String(attribut e.value)); 606 String strippedValue = stripLeadingAndTrailingHTMLSpaces(String(attribut e.value));
561 bool valueContainsJavaScriptURL = (!isInlineEventHandler && protocolIsJa vaScript(strippedValue)) || (isSemicolonSeparatedAttribute(attribute) && semicol onSeparatedValueContainsJavaScriptURL(strippedValue)); 607 bool valueContainsJavaScriptURL = (!isInlineEventHandler && protocolIsJa vaScript(strippedValue)) || (isSemicolonSeparatedAttribute(attribute) && semicol onSeparatedValueContainsJavaScriptURL(strippedValue));
562 if (!isInlineEventHandler && !valueContainsJavaScriptURL) 608 if (!isInlineEventHandler && !valueContainsJavaScriptURL)
563 continue; 609 continue;
564 if (!isContainedInRequest(decodedSnippetForAttribute(request, attribute, ScriptLikeAttribute))) 610 if (!isContainedInRequest(canonicalize(snippetFromAttribute(request, att ribute), ScriptLikeAttributeTruncation)))
565 continue; 611 continue;
566 request.token.eraseValueOfAttribute(i); 612 request.token.eraseValueOfAttribute(i);
567 if (valueContainsJavaScriptURL) 613 if (valueContainsJavaScriptURL)
568 request.token.appendToAttributeValue(i, safeJavaScriptURL); 614 request.token.appendToAttributeValue(i, safeJavaScriptURL);
569 didBlockScript = true; 615 didBlockScript = true;
570 } 616 }
571 return didBlockScript; 617 return didBlockScript;
572 } 618 }
573 619
574 bool XSSAuditor::eraseAttributeIfInjected(const FilterTokenRequest& request, con st QualifiedName& attributeName, const String& replacementValue, AttributeKind t reatment) 620 bool XSSAuditor::eraseAttributeIfInjected(const FilterTokenRequest& request, con st QualifiedName& attributeName, const String& replacementValue, TruncationKind treatment)
575 { 621 {
576 size_t indexOfAttribute = 0; 622 size_t indexOfAttribute = 0;
577 if (findAttributeWithName(request.token, attributeName, indexOfAttribute)) { 623 if (!findAttributeWithName(request.token, attributeName, indexOfAttribute))
578 const HTMLToken::Attribute& attribute = request.token.attributes().at(in dexOfAttribute); 624 return false;
579 if (isContainedInRequest(decodedSnippetForAttribute(request, attribute, treatment))) { 625
580 if (threadSafeMatch(attributeName, srcAttr) && isLikelySafeResource( String(attribute.value))) 626 const HTMLToken::Attribute& attribute = request.token.attributes().at(indexO fAttribute);
581 return false; 627 if (!isContainedInRequest(canonicalize(snippetFromAttribute(request, attribu te), treatment)))
582 if (threadSafeMatch(attributeName, http_equivAttr) && !isDangerousHT TPEquiv(String(attribute.value))) 628 return false;
583 return false; 629
584 request.token.eraseValueOfAttribute(indexOfAttribute); 630 if (threadSafeMatch(attributeName, srcAttr)) {
585 if (!replacementValue.isEmpty()) 631 if (isLikelySafeResource(String(attribute.value)))
586 request.token.appendToAttributeValue(indexOfAttribute, replaceme ntValue); 632 return false;
587 return true; 633 } else if (threadSafeMatch(attributeName, http_equivAttr)) {
588 } 634 if (!isDangerousHTTPEquiv(String(attribute.value)))
635 return false;
589 } 636 }
590 return false; 637
638 request.token.eraseValueOfAttribute(indexOfAttribute);
639 if (!replacementValue.isEmpty())
640 request.token.appendToAttributeValue(indexOfAttribute, replacementValue) ;
641
642 return true;
591 } 643 }
592 644
593 String XSSAuditor::decodedSnippetForName(const FilterTokenRequest& request) 645 String XSSAuditor::canonicalizedSnippetForTagName(const FilterTokenRequest& requ est)
594 { 646 {
595 // Grab a fixed number of characters equal to the length of the token's name plus one (to account for the "<"). 647 // Grab a fixed number of characters equal to the length of the token's name plus one (to account for the "<").
596 return canonicalize(fullyDecodeString(request.sourceTracker.sourceForToken(r equest.token), m_encoding).substring(0, request.token.name().size() + 1)); 648 return canonicalize(request.sourceTracker.sourceForToken(request.token).subs tring(0, request.token.name().size() + 1), NoTruncation);
597 } 649 }
598 650
599 String XSSAuditor::decodedSnippetForAttribute(const FilterTokenRequest& request, const HTMLToken::Attribute& attribute, AttributeKind treatment) 651 String XSSAuditor::snippetFromAttribute(const FilterTokenRequest& request, const HTMLToken::Attribute& attribute)
600 { 652 {
601 // The range doesn't inlcude the character which terminates the value. So, 653 // The range doesn't inlcude the character which terminates the value. So,
602 // for an input of |name="value"|, the snippet is |name="value|. For an 654 // for an input of |name="value"|, the snippet is |name="value|. For an
603 // unquoted input of |name=value |, the snippet is |name=value|. 655 // unquoted input of |name=value |, the snippet is |name=value|.
604 // FIXME: We should grab one character before the name also. 656 // FIXME: We should grab one character before the name also.
605 int start = attribute.nameRange.start - request.token.startIndex(); 657 int start = attribute.nameRange.start - request.token.startIndex();
606 int end = attribute.valueRange.end - request.token.startIndex(); 658 int end = attribute.valueRange.end - request.token.startIndex();
607 String decodedSnippet = fullyDecodeString(request.sourceTracker.sourceForTok en(request.token).substring(start, end - start), m_encoding); 659 return request.sourceTracker.sourceForToken(request.token).substring(start, end - start);
608 decodedSnippet.truncate(kMaximumFragmentLengthTarget);
609 if (treatment == SrcLikeAttribute) {
610 int slashCount = 0;
611 bool commaSeen = false;
612 // In HTTP URLs, characters following the first ?, #, or third slash may come from
613 // the page itself and can be merely ignored by an attacker's server whe n a remote
614 // script or script-like resource is requested. In DATA URLS, the payloa d starts at
615 // the first comma, and the the first /*, //, or <!-- may introduce a co mment. Characters
616 // following this may come from the page itself and may be ignored when the script is
617 // executed. For simplicity, we don't differentiate based on URL scheme, and stop at
618 // the first # or ?, the third slash, or the first slash or < once a com ma is seen.
619 for (size_t currentLength = 0; currentLength < decodedSnippet.length(); ++currentLength) {
620 UChar currentChar = decodedSnippet[currentLength];
621 if (currentChar == '?'
622 || currentChar == '#'
623 || ((currentChar == '/' || currentChar == '\\') && (commaSeen || ++slashCount > 2))
624 || (currentChar == '<' && commaSeen)) {
625 decodedSnippet.truncate(currentLength);
626 break;
627 }
628 if (currentChar == ',')
629 commaSeen = true;
630 }
631 } else if (treatment == ScriptLikeAttribute) {
632 // Beware of trailing characters which came from the page itself, not th e
633 // injected vector. Excluding the terminating character covers common ca ses
634 // where the page immediately ends the attribute, but doesn't cover more
635 // complex cases where there is other page data following the injection.
636 // Generally, these won't parse as javascript, so the injected vector
637 // typically excludes them from consideration via a single-line comment or
638 // by enclosing them in a string literal terminated later by the page's own
639 // closing punctuation. Since the snippet has not been parsed, the vecto r
640 // may also try to introduce these via entities. As a result, we'd like to
641 // stop before the first "//", the first <!--, the first entity, or the first
642 // quote not immediately following the first equals sign (taking whitesp ace
643 // into consideration). To keep things simpler, we don't try to distingu ish
644 // between entity-introducing amperands vs. other uses, nor do we bother to
645 // check for a second slash for a comment, nor do we bother to check for
646 // !-- following a less-than sign. We stop instead on any ampersand
647 // slash, or less-than sign.
648 size_t position = 0;
649 if ((position = decodedSnippet.find("=")) != kNotFound
650 && (position = decodedSnippet.find(isNotHTMLSpace<UChar>, position + 1)) != kNotFound
651 && (position = decodedSnippet.find(isTerminatingCharacter, isHTMLQuo te(decodedSnippet[position]) ? position + 1 : position)) != kNotFound) {
652 decodedSnippet.truncate(position);
653 }
654 }
655 return canonicalize(decodedSnippet);
656 } 660 }
657 661
658 String XSSAuditor::decodedSnippetForJavaScript(const FilterTokenRequest& request ) 662 String XSSAuditor::canonicalize(String snippet, TruncationKind treatment)
663 {
664 String decodedSnippet = fullyDecodeString(snippet, m_encoding);
665
666 if (treatment != NoTruncation) {
667 decodedSnippet.truncate(kMaximumFragmentLengthTarget);
668 if (treatment == SrcLikeAttributeTruncation)
669 truncateForSrcLikeAttribute(decodedSnippet);
670 else if (treatment == ScriptLikeAttributeTruncation)
671 truncateForScriptLikeAttribute(decodedSnippet);
672 }
673
674 return decodedSnippet.removeCharacters(&isNonCanonicalCharacter);
675 }
676
677 String XSSAuditor::canonicalizedSnippetForJavaScript(const FilterTokenRequest& r equest)
659 { 678 {
660 String string = request.sourceTracker.sourceForToken(request.token); 679 String string = request.sourceTracker.sourceForToken(request.token);
661 size_t startPosition = 0; 680 size_t startPosition = 0;
662 size_t endPosition = string.length(); 681 size_t endPosition = string.length();
663 size_t foundPosition = kNotFound; 682 size_t foundPosition = kNotFound;
664 size_t lastNonSpacePosition = kNotFound; 683 size_t lastNonSpacePosition = kNotFound;
665 684
666 // Skip over initial comments to find start of code. 685 // Skip over initial comments to find start of code.
667 while (startPosition < endPosition) { 686 while (startPosition < endPosition) {
668 while (startPosition < endPosition && isHTMLSpace<UChar>(string[startPos ition])) 687 while (startPosition < endPosition && isHTMLSpace<UChar>(string[startPos ition]))
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
702 break; 721 break;
703 } 722 }
704 } 723 }
705 if (string[foundPosition] == ',') 724 if (string[foundPosition] == ',')
706 break; 725 break;
707 726
708 if (lastNonSpacePosition != kNotFound && startsOpeningScriptTagAt(st ring, foundPosition)) { 727 if (lastNonSpacePosition != kNotFound && startsOpeningScriptTagAt(st ring, foundPosition)) {
709 foundPosition = lastNonSpacePosition; 728 foundPosition = lastNonSpacePosition;
710 break; 729 break;
711 } 730 }
712
713 if (foundPosition > startPosition + kMaximumFragmentLengthTarget) { 731 if (foundPosition > startPosition + kMaximumFragmentLengthTarget) {
714 // After hitting the length target, we can only stop at a point where we know we are 732 // After hitting the length target, we can only stop at a point where we know we are
715 // not in the middle of a %-escape sequence. For the sake of sim plicity, approximate 733 // not in the middle of a %-escape sequence. For the sake of sim plicity, approximate
716 // not stopping inside a (possibly multiply encoded) %-escape se quence by breaking on 734 // not stopping inside a (possibly multiply encoded) %-escape se quence by breaking on
717 // whitespace only. We should have enough text in these cases to avoid false positives. 735 // whitespace only. We should have enough text in these cases to avoid false positives.
718 if (isHTMLSpace<UChar>(string[foundPosition])) 736 if (isHTMLSpace<UChar>(string[foundPosition]))
719 break; 737 break;
720 } 738 }
721
722 if (!isHTMLSpace<UChar>(string[foundPosition])) 739 if (!isHTMLSpace<UChar>(string[foundPosition]))
723 lastNonSpacePosition = foundPosition; 740 lastNonSpacePosition = foundPosition;
724 } 741 }
725 742 result = canonicalize(string.substring(startPosition, foundPosition - st artPosition), NoTruncation);
726 result = canonicalize(fullyDecodeString(string.substring(startPosition, foundPosition - startPosition), m_encoding));
727 startPosition = foundPosition + 1; 743 startPosition = foundPosition + 1;
728 } 744 }
745
729 return result; 746 return result;
730 } 747 }
731 748
732 bool XSSAuditor::isContainedInRequest(const String& decodedSnippet) 749 bool XSSAuditor::isContainedInRequest(const String& decodedSnippet)
733 { 750 {
734 if (decodedSnippet.isEmpty()) 751 if (decodedSnippet.isEmpty())
735 return false; 752 return false;
736 if (m_decodedURL.find(decodedSnippet, 0, false) != kNotFound) 753 if (m_decodedURL.find(decodedSnippet, 0, false) != kNotFound)
737 return true; 754 return true;
738 if (m_decodedHTTPBodySuffixTree && !m_decodedHTTPBodySuffixTree->mightContai n(decodedSnippet)) 755 if (m_decodedHTTPBodySuffixTree && !m_decodedHTTPBodySuffixTree->mightContai n(decodedSnippet))
(...skipping 24 matching lines...) Expand all
763 780
764 bool XSSAuditor::isSafeToSendToAnotherThread() const 781 bool XSSAuditor::isSafeToSendToAnotherThread() const
765 { 782 {
766 return m_documentURL.isSafeToSendToAnotherThread() 783 return m_documentURL.isSafeToSendToAnotherThread()
767 && m_decodedURL.isSafeToSendToAnotherThread() 784 && m_decodedURL.isSafeToSendToAnotherThread()
768 && m_decodedHTTPBody.isSafeToSendToAnotherThread() 785 && m_decodedHTTPBody.isSafeToSendToAnotherThread()
769 && m_httpBodyAsString.isSafeToSendToAnotherThread(); 786 && m_httpBodyAsString.isSafeToSendToAnotherThread();
770 } 787 }
771 788
772 } // namespace WebCore 789 } // namespace WebCore
OLDNEW
« no previous file with comments | « Source/core/html/parser/XSSAuditor.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698