Changeset 108551 in webkit
- Timestamp:
- Feb 22, 2012 2:21:14 PM (12 years ago)
- Location:
- trunk
- Files:
-
- 8 added
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r108548 r108551 1 2012-02-22 Tom Sepez <tsepez@chromium.org> 2 3 XSSAuditor bypass with <svg> tags and html-entities. 4 https://bugs.webkit.org/show_bug.cgi?id=78836 5 6 Reviewed by Adam Barth. 7 8 * http/tests/security/xssAuditor/iframe-onload-in-svg-tag-expected.txt: Added. 9 * http/tests/security/xssAuditor/iframe-onload-in-svg-tag.html: Added. 10 * http/tests/security/xssAuditor/script-tag-inside-svg-tag-expected.txt: Added. 11 * http/tests/security/xssAuditor/script-tag-inside-svg-tag.html: Added. 12 * http/tests/security/xssAuditor/script-tag-inside-svg-tag2-expected.txt: Added. 13 * http/tests/security/xssAuditor/script-tag-inside-svg-tag2.html: Added. 14 * http/tests/security/xssAuditor/script-tag-inside-svg-tag3-expected.txt: Added. 15 * http/tests/security/xssAuditor/script-tag-inside-svg-tag3.html: Added. 16 1 17 2012-02-22 Ken Buchanan <kenrb@chromium.org> 2 18 -
trunk/Source/WebCore/ChangeLog
r108550 r108551 1 2012-02-22 Tom Sepez <tsepez@chromium.org> 2 3 XSSAuditor bypass with <svg> tags and html-entities. 4 https://bugs.webkit.org/show_bug.cgi?id=78836 5 6 Reviewed by Adam Barth. 7 8 Tests: http/tests/security/xssAuditor/iframe-onload-in-svg-tag.html 9 http/tests/security/xssAuditor/script-tag-inside-svg-tag.html 10 http/tests/security/xssAuditor/script-tag-inside-svg-tag2.html 11 http/tests/security/xssAuditor/script-tag-inside-svg-tag3.html 12 13 * html/parser/XSSAuditor.cpp: 14 (WebCore::isNonCanonicalCharacter): 15 (WebCore::isTerminatingCharacter): 16 (WebCore): 17 (WebCore::startsHTMLCommentAt): 18 (WebCore::startsSingleLineCommentAt): 19 (WebCore::fullyDecodeString): 20 (WebCore::XSSAuditor::XSSAuditor): 21 (WebCore::XSSAuditor::init): 22 (WebCore::XSSAuditor::filterToken): 23 (WebCore::XSSAuditor::filterStartToken): 24 (WebCore::XSSAuditor::filterEndToken): 25 (WebCore::XSSAuditor::filterCharacterToken): 26 (WebCore::XSSAuditor::filterScriptToken): 27 (WebCore::XSSAuditor::filterObjectToken): 28 (WebCore::XSSAuditor::filterParamToken): 29 (WebCore::XSSAuditor::filterEmbedToken): 30 (WebCore::XSSAuditor::filterAppletToken): 31 (WebCore::XSSAuditor::filterIframeToken): 32 (WebCore::XSSAuditor::filterMetaToken): 33 (WebCore::XSSAuditor::filterBaseToken): 34 (WebCore::XSSAuditor::filterFormToken): 35 (WebCore::XSSAuditor::decodedSnippetForAttribute): 36 (WebCore::XSSAuditor::snippetForJavaScript): 37 * html/parser/XSSAuditor.h: 38 (XSSAuditor): 39 1 40 2012-02-22 Anders Carlsson <andersca@apple.com> 2 41 -
trunk/Source/WebCore/html/parser/XSSAuditor.cpp
r107967 r108551 37 37 #include "HTMLDocumentParser.h" 38 38 #include "HTMLNames.h" 39 #include "HTMLTokenizer.h" 39 40 #include "HTMLParamElement.h" 40 41 #include "HTMLParserIdioms.h" … … 55 56 // 56 57 // Note, we don't remove backslashes like PHP stripslashes(), which among other things converts "\\0" to the \0 character. 57 // Instead, we remove backslashes and zeros (since the string "\\0" =(remove backslashes)=> "0"). However, this has the 58 // Instead, we remove backslashes and zeros (since the string "\\0" =(remove backslashes)=> "0"). However, this has the 58 59 // adverse effect that we remove any legitimate zeros from a string. 59 60 // … … 72 73 } 73 74 74 static bool isTerminatingCharacter(UChar c) 75 static bool isTerminatingCharacter(UChar c) 75 76 { 76 77 return (c == '&' || c == '/' || c == '"' || c == '\'' || c == '<'); … … 88 89 } 89 90 90 static bool startsHTMLEndTagAt(const String& string, size_t start)91 {92 return (start + 1 < string.length() && string[start] == '<' && string[start+1] == '/');93 }94 95 96 91 static bool startsHTMLCommentAt(const String& string, size_t start) 97 92 { 98 93 return (start + 3 < string.length() && string[start] == '<' && string[start+1] == '!' && string[start+2] == '-' && string[start+3] == '-'); 99 } 94 } 100 95 101 96 static bool startsSingleLineCommentAt(const String& string, size_t start) 102 97 { 103 98 return (start + 1 < string.length() && string[start] == '/' && string[start+1] == '/'); 104 } 99 } 105 100 106 101 static bool startsMultiLineCommentAt(const String& string, size_t start) … … 178 173 workingString = decode16BitUnicodeEscapeSequences(decodeStandardURLEscapeSequences(workingString, encoding)); 179 174 } while (workingString.length() < oldWorkingStringLength); 180 ASSERT(!workingString.isEmpty());181 175 workingString.replace('+', ' '); 182 176 workingString = canonicalize(workingString); … … 189 183 , m_xssProtection(XSSProtectionEnabled) 190 184 , m_state(Uninitialized) 185 , m_shouldAllowCDATA(false) 186 , m_scriptTagNestingLevel(0) 191 187 , m_notifiedClient(false) 192 188 { … … 206 202 207 203 ASSERT(m_state == Uninitialized); 208 m_state = Initial ;204 m_state = Initialized; 209 205 210 206 if (!m_isEnabled) … … 259 255 void XSSAuditor::filterToken(HTMLToken& token) 260 256 { 261 if (m_state == Uninitialized) {257 if (m_state == Uninitialized) 262 258 init(); 263 ASSERT(m_state == Initial); 264 } 265 259 260 ASSERT(m_state == Initialized); 266 261 if (!m_isEnabled || m_xssProtection == XSSProtectionDisabled) 267 262 return; 268 263 269 264 bool didBlockScript = false; 270 271 switch (m_state) { 272 case Uninitialized: 273 ASSERT_NOT_REACHED(); 274 break; 275 case Initial: 276 didBlockScript = filterTokenInitial(token); 277 break; 278 case AfterScriptStartTag: 279 didBlockScript = filterTokenAfterScriptStartTag(token); 280 ASSERT(m_state == Initial); 281 m_cachedSnippet = String(); 282 break; 265 if (token.type() == HTMLTokenTypes::StartTag) 266 didBlockScript = filterStartToken(token); 267 else if (m_scriptTagNestingLevel) { 268 if (token.type() == HTMLTokenTypes::Character) 269 didBlockScript = filterCharacterToken(token); 270 else if (token.type() == HTMLTokenTypes::EndTag) 271 filterEndToken(token); 283 272 } 284 273 … … 302 291 } 303 292 304 bool XSSAuditor::filterTokenInitial(HTMLToken& token) 305 { 306 ASSERT(m_state == Initial); 307 308 if (token.type() != HTMLTokenTypes::StartTag) 309 return false; 310 293 bool XSSAuditor::filterStartToken(HTMLToken& token) 294 { 311 295 bool didBlockScript = eraseDangerousAttributesIfInjected(token); 312 296 313 if (hasName(token, scriptTag)) 297 if (hasName(token, scriptTag)) { 314 298 didBlockScript |= filterScriptToken(token); 315 else if (hasName(token, objectTag)) 299 ASSERT(m_shouldAllowCDATA || !m_scriptTagNestingLevel); 300 m_scriptTagNestingLevel++; 301 } else if (hasName(token, objectTag)) 316 302 didBlockScript |= filterObjectToken(token); 317 303 else if (hasName(token, paramTag)) … … 333 319 } 334 320 335 bool XSSAuditor::filterTokenAfterScriptStartTag(HTMLToken& token) 336 { 337 ASSERT(m_state == AfterScriptStartTag); 338 m_state = Initial; 339 340 if (token.type() != HTMLTokenTypes::Character) { 341 ASSERT(token.type() == HTMLTokenTypes::EndTag || token.type() == HTMLTokenTypes::EndOfFile); 342 return false; 343 } 344 321 void XSSAuditor::filterEndToken(HTMLToken& token) 322 { 323 ASSERT(m_scriptTagNestingLevel); 324 if (hasName(token, scriptTag)) { 325 m_scriptTagNestingLevel--; 326 ASSERT(m_shouldAllowCDATA || !m_scriptTagNestingLevel); 327 } 328 } 329 330 bool XSSAuditor::filterCharacterToken(HTMLToken& token) 331 { 332 ASSERT(m_scriptTagNestingLevel); 345 333 TextResourceDecoder* decoder = m_parser->document()->decoder(); 346 334 if (isContainedInRequest(fullyDecodeString(m_cachedSnippet, decoder))) { … … 359 347 bool XSSAuditor::filterScriptToken(HTMLToken& token) 360 348 { 361 ASSERT(m_state == Initial);362 349 ASSERT(token.type() == HTMLTokenTypes::StartTag); 363 350 ASSERT(hasName(token, scriptTag)); … … 366 353 return true; 367 354 368 m_state = AfterScriptStartTag;369 355 m_cachedSnippet = m_parser->sourceForToken(token); 356 m_shouldAllowCDATA = m_parser->tokenizer()->shouldAllowCDATA(); 370 357 return false; 371 358 } … … 373 360 bool XSSAuditor::filterObjectToken(HTMLToken& token) 374 361 { 375 ASSERT(m_state == Initial);376 362 ASSERT(token.type() == HTMLTokenTypes::StartTag); 377 363 ASSERT(hasName(token, objectTag)); … … 388 374 bool XSSAuditor::filterParamToken(HTMLToken& token) 389 375 { 390 ASSERT(m_state == Initial);391 376 ASSERT(token.type() == HTMLTokenTypes::StartTag); 392 377 ASSERT(hasName(token, paramTag)); … … 407 392 bool XSSAuditor::filterEmbedToken(HTMLToken& token) 408 393 { 409 ASSERT(m_state == Initial);410 394 ASSERT(token.type() == HTMLTokenTypes::StartTag); 411 395 ASSERT(hasName(token, embedTag)); … … 422 406 bool XSSAuditor::filterAppletToken(HTMLToken& token) 423 407 { 424 ASSERT(m_state == Initial);425 408 ASSERT(token.type() == HTMLTokenTypes::StartTag); 426 409 ASSERT(hasName(token, appletTag)); … … 436 419 bool XSSAuditor::filterIframeToken(HTMLToken& token) 437 420 { 438 ASSERT(m_state == Initial);439 421 ASSERT(token.type() == HTMLTokenTypes::StartTag); 440 422 ASSERT(hasName(token, iframeTag)); … … 445 427 bool XSSAuditor::filterMetaToken(HTMLToken& token) 446 428 { 447 ASSERT(m_state == Initial);448 429 ASSERT(token.type() == HTMLTokenTypes::StartTag); 449 430 ASSERT(hasName(token, metaTag)); … … 454 435 bool XSSAuditor::filterBaseToken(HTMLToken& token) 455 436 { 456 ASSERT(m_state == Initial);457 437 ASSERT(token.type() == HTMLTokenTypes::StartTag); 458 438 ASSERT(hasName(token, baseTag)); … … 463 443 bool XSSAuditor::filterFormToken(HTMLToken& token) 464 444 { 465 ASSERT(m_state == Initial);466 445 ASSERT(token.type() == HTMLTokenTypes::StartTag); 467 446 ASSERT(hasName(token, formTag)); … … 542 521 String XSSAuditor::decodedSnippetForAttribute(const HTMLToken& token, const HTMLToken::Attribute& attribute, AttributeKind treatment) 543 522 { 544 const size_t kMaximumSnippetLength = 100;545 546 523 // The range doesn't inlcude the character which terminates the value. So, 547 524 // for an input of |name="value"|, the snippet is |name="value|. For an … … 551 528 int end = attribute.m_valueRange.m_end - token.startIndex(); 552 529 String decodedSnippet = fullyDecodeString(snippetForRange(token, start, end), m_parser->document()->decoder()); 553 decodedSnippet.truncate(kMaximum SnippetLength);530 decodedSnippet.truncate(kMaximumFragmentLengthTarget); 554 531 if (treatment == SrcLikeAttribute) { 555 532 int slashCount; … … 594 571 String XSSAuditor::snippetForJavaScript(const String& string) 595 572 { 596 const size_t kMaximumFragmentLengthTarget = 100;597 598 573 size_t startPosition = 0; 599 574 size_t endPosition = string.length(); … … 604 579 while (startPosition < endPosition && isHTMLSpace(string[startPosition])) 605 580 startPosition++; 581 582 // Under SVG/XML rules, only HTML comment syntax matters and the parser returns 583 // these as a separate comment tokens. Having consumed whitespace, we need not look 584 // further for these. 585 if (m_shouldAllowCDATA) 586 break; 587 588 // Under HTML rules, both the HTML and JS comment synatx matters, and the HTML 589 // comment ends at the end of the line, not with -->. 606 590 if (startsHTMLCommentAt(string, startPosition) || startsSingleLineCommentAt(string, startPosition)) { 607 591 while (startPosition < endPosition && !isJSNewline(string[startPosition])) … … 616 600 } 617 601 618 // Stop at next comment, or at a closing script tag (which may have been included with 619 // the code fragment because of buffering in the HTMLSourceTracker), or when we exceed 620 // the maximum length target. After hitting the length target, we can only stop at a 621 // point where we know we are not in the middle of a %-escape sequence. For the sake of 622 // simplicity, approximate stopping at a close script tag by stopping at any close tag, 623 // and approximate not stopping inside a (possibly multiply encoded) %-esacpe sequence 624 // by breaking on whitespace only. We should have enough text in these cases to avoid 625 // false positives. 602 // Stop at next comment (using the same rules as above for SVG/XML vs HTML), or when 603 // we exceed the maximum length target. After hitting the length target, we can only 604 // stop at a point where we know we are not in the middle of a %-escape sequence. For 605 // the sake of simplicity, approximate not stopping inside a (possibly multiply encoded) 606 // %-esacpe sequence by breaking on whitespace only. We should have enough text in 607 // these cases to avoid false positives. 626 608 for (foundPosition = startPosition; foundPosition < endPosition; foundPosition++) { 627 if (startsSingleLineCommentAt(string, foundPosition) || startsMultiLineCommentAt(string, foundPosition) || startsHTMLEndTagAt(string, foundPosition)) { 628 endPosition = foundPosition + 2; 629 break; 630 } 631 if (startsHTMLCommentAt(string, foundPosition)) { 632 endPosition = foundPosition + 4; 633 break; 609 if (!m_shouldAllowCDATA) { 610 if (startsSingleLineCommentAt(string, foundPosition) || startsMultiLineCommentAt(string, foundPosition)) { 611 endPosition = foundPosition + 2; 612 break; 613 } 614 if (startsHTMLCommentAt(string, foundPosition)) { 615 endPosition = foundPosition + 4; 616 break; 617 } 634 618 } 635 619 if (foundPosition > startPosition + kMaximumFragmentLengthTarget && isHTMLSpace(string[foundPosition])) { … … 638 622 } 639 623 } 640 624 641 625 return string.substring(startPosition, endPosition - startPosition); 642 626 } -
trunk/Source/WebCore/html/parser/XSSAuditor.h
r99096 r108551 43 43 44 44 private: 45 static const size_t kMaximumFragmentLengthTarget = 100; 46 45 47 enum State { 46 48 Uninitialized, 47 Initial, 48 AfterScriptStartTag, 49 Initialized 49 50 }; 50 51 … … 56 57 void init(); 57 58 58 bool filter TokenInitial(HTMLToken&);59 bool filterTokenAfterScriptStartTag(HTMLToken&);60 59 bool filterStartToken(HTMLToken&); 60 void filterEndToken(HTMLToken&); 61 bool filterCharacterToken(HTMLToken&); 61 62 bool filterScriptToken(HTMLToken&); 62 63 bool filterObjectToken(HTMLToken&); … … 89 90 State m_state; 90 91 String m_cachedSnippet; 92 bool m_shouldAllowCDATA; 93 unsigned m_scriptTagNestingLevel; 91 94 bool m_notifiedClient; 92 95 };
Note: See TracChangeset
for help on using the changeset viewer.