Changeset 108551 in webkit


Ignore:
Timestamp:
Feb 22, 2012 2:21:14 PM (12 years ago)
Author:
tsepez@chromium.org
Message:

XSSAuditor bypass with <svg> tags and html-entities.
https://bugs.webkit.org/show_bug.cgi?id=78836

Reviewed by Adam Barth.

Source/WebCore:

Tests: http/tests/security/xssAuditor/iframe-onload-in-svg-tag.html

http/tests/security/xssAuditor/script-tag-inside-svg-tag.html
http/tests/security/xssAuditor/script-tag-inside-svg-tag2.html
http/tests/security/xssAuditor/script-tag-inside-svg-tag3.html

  • html/parser/XSSAuditor.cpp:

(WebCore::isNonCanonicalCharacter):
(WebCore::isTerminatingCharacter):
(WebCore):
(WebCore::startsHTMLCommentAt):
(WebCore::startsSingleLineCommentAt):
(WebCore::fullyDecodeString):
(WebCore::XSSAuditor::XSSAuditor):
(WebCore::XSSAuditor::init):
(WebCore::XSSAuditor::filterToken):
(WebCore::XSSAuditor::filterStartToken):
(WebCore::XSSAuditor::filterEndToken):
(WebCore::XSSAuditor::filterCharacterToken):
(WebCore::XSSAuditor::filterScriptToken):
(WebCore::XSSAuditor::filterObjectToken):
(WebCore::XSSAuditor::filterParamToken):
(WebCore::XSSAuditor::filterEmbedToken):
(WebCore::XSSAuditor::filterAppletToken):
(WebCore::XSSAuditor::filterIframeToken):
(WebCore::XSSAuditor::filterMetaToken):
(WebCore::XSSAuditor::filterBaseToken):
(WebCore::XSSAuditor::filterFormToken):
(WebCore::XSSAuditor::decodedSnippetForAttribute):
(WebCore::XSSAuditor::snippetForJavaScript):

  • html/parser/XSSAuditor.h:

(XSSAuditor):

LayoutTests:

  • http/tests/security/xssAuditor/iframe-onload-in-svg-tag-expected.txt: Added.
  • http/tests/security/xssAuditor/iframe-onload-in-svg-tag.html: Added.
  • http/tests/security/xssAuditor/script-tag-inside-svg-tag-expected.txt: Added.
  • http/tests/security/xssAuditor/script-tag-inside-svg-tag.html: Added.
  • http/tests/security/xssAuditor/script-tag-inside-svg-tag2-expected.txt: Added.
  • http/tests/security/xssAuditor/script-tag-inside-svg-tag2.html: Added.
  • http/tests/security/xssAuditor/script-tag-inside-svg-tag3-expected.txt: Added.
  • http/tests/security/xssAuditor/script-tag-inside-svg-tag3.html: Added.
Location:
trunk
Files:
8 added
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r108548 r108551  
     12012-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
    1172012-02-22  Ken Buchanan  <kenrb@chromium.org>
    218
  • trunk/Source/WebCore/ChangeLog

    r108550 r108551  
     12012-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
    1402012-02-22  Anders Carlsson  <andersca@apple.com>
    241
  • trunk/Source/WebCore/html/parser/XSSAuditor.cpp

    r107967 r108551  
    3737#include "HTMLDocumentParser.h"
    3838#include "HTMLNames.h"
     39#include "HTMLTokenizer.h"
    3940#include "HTMLParamElement.h"
    4041#include "HTMLParserIdioms.h"
     
    5556    //
    5657    // 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
    5859    // adverse effect that we remove any legitimate zeros from a string.
    5960    //
     
    7273}
    7374
    74 static bool isTerminatingCharacter(UChar c) 
     75static bool isTerminatingCharacter(UChar c)
    7576{
    7677    return (c == '&' || c == '/' || c == '"' || c == '\'' || c == '<');
     
    8889}
    8990
    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 
    9691static bool startsHTMLCommentAt(const String& string, size_t start)
    9792{
    9893    return (start + 3 < string.length() && string[start] == '<' && string[start+1] == '!' && string[start+2] == '-' && string[start+3] == '-');
    99 }   
     94}
    10095
    10196static bool startsSingleLineCommentAt(const String& string, size_t start)
    10297{
    10398    return (start + 1 < string.length() && string[start] == '/' && string[start+1] == '/');
    104 }   
     99}
    105100
    106101static bool startsMultiLineCommentAt(const String& string, size_t start)
     
    178173        workingString = decode16BitUnicodeEscapeSequences(decodeStandardURLEscapeSequences(workingString, encoding));
    179174    } while (workingString.length() < oldWorkingStringLength);
    180     ASSERT(!workingString.isEmpty());
    181175    workingString.replace('+', ' ');
    182176    workingString = canonicalize(workingString);
     
    189183    , m_xssProtection(XSSProtectionEnabled)
    190184    , m_state(Uninitialized)
     185    , m_shouldAllowCDATA(false)
     186    , m_scriptTagNestingLevel(0)
    191187    , m_notifiedClient(false)
    192188{
     
    206202
    207203    ASSERT(m_state == Uninitialized);
    208     m_state = Initial;
     204    m_state = Initialized;
    209205
    210206    if (!m_isEnabled)
     
    259255void XSSAuditor::filterToken(HTMLToken& token)
    260256{
    261     if (m_state == Uninitialized) {
     257    if (m_state == Uninitialized)
    262258        init();
    263         ASSERT(m_state == Initial);
    264     }
    265 
     259   
     260    ASSERT(m_state == Initialized);
    266261    if (!m_isEnabled || m_xssProtection == XSSProtectionDisabled)
    267262        return;
    268263
    269264    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);
    283272    }
    284273
     
    302291}
    303292
    304 bool XSSAuditor::filterTokenInitial(HTMLToken& token)
    305 {
    306     ASSERT(m_state == Initial);
    307 
    308     if (token.type() != HTMLTokenTypes::StartTag)
    309         return false;
    310 
     293bool XSSAuditor::filterStartToken(HTMLToken& token)
     294{
    311295    bool didBlockScript = eraseDangerousAttributesIfInjected(token);
    312296
    313     if (hasName(token, scriptTag))
     297    if (hasName(token, scriptTag)) {
    314298        didBlockScript |= filterScriptToken(token);
    315     else if (hasName(token, objectTag))
     299        ASSERT(m_shouldAllowCDATA || !m_scriptTagNestingLevel);
     300        m_scriptTagNestingLevel++;
     301    } else if (hasName(token, objectTag))
    316302        didBlockScript |= filterObjectToken(token);
    317303    else if (hasName(token, paramTag))
     
    333319}
    334320
    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 
     321void 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
     330bool XSSAuditor::filterCharacterToken(HTMLToken& token)
     331{
     332    ASSERT(m_scriptTagNestingLevel);
    345333    TextResourceDecoder* decoder = m_parser->document()->decoder();
    346334    if (isContainedInRequest(fullyDecodeString(m_cachedSnippet, decoder))) {
     
    359347bool XSSAuditor::filterScriptToken(HTMLToken& token)
    360348{
    361     ASSERT(m_state == Initial);
    362349    ASSERT(token.type() == HTMLTokenTypes::StartTag);
    363350    ASSERT(hasName(token, scriptTag));
     
    366353        return true;
    367354
    368     m_state = AfterScriptStartTag;
    369355    m_cachedSnippet = m_parser->sourceForToken(token);
     356    m_shouldAllowCDATA = m_parser->tokenizer()->shouldAllowCDATA();
    370357    return false;
    371358}
     
    373360bool XSSAuditor::filterObjectToken(HTMLToken& token)
    374361{
    375     ASSERT(m_state == Initial);
    376362    ASSERT(token.type() == HTMLTokenTypes::StartTag);
    377363    ASSERT(hasName(token, objectTag));
     
    388374bool XSSAuditor::filterParamToken(HTMLToken& token)
    389375{
    390     ASSERT(m_state == Initial);
    391376    ASSERT(token.type() == HTMLTokenTypes::StartTag);
    392377    ASSERT(hasName(token, paramTag));
     
    407392bool XSSAuditor::filterEmbedToken(HTMLToken& token)
    408393{
    409     ASSERT(m_state == Initial);
    410394    ASSERT(token.type() == HTMLTokenTypes::StartTag);
    411395    ASSERT(hasName(token, embedTag));
     
    422406bool XSSAuditor::filterAppletToken(HTMLToken& token)
    423407{
    424     ASSERT(m_state == Initial);
    425408    ASSERT(token.type() == HTMLTokenTypes::StartTag);
    426409    ASSERT(hasName(token, appletTag));
     
    436419bool XSSAuditor::filterIframeToken(HTMLToken& token)
    437420{
    438     ASSERT(m_state == Initial);
    439421    ASSERT(token.type() == HTMLTokenTypes::StartTag);
    440422    ASSERT(hasName(token, iframeTag));
     
    445427bool XSSAuditor::filterMetaToken(HTMLToken& token)
    446428{
    447     ASSERT(m_state == Initial);
    448429    ASSERT(token.type() == HTMLTokenTypes::StartTag);
    449430    ASSERT(hasName(token, metaTag));
     
    454435bool XSSAuditor::filterBaseToken(HTMLToken& token)
    455436{
    456     ASSERT(m_state == Initial);
    457437    ASSERT(token.type() == HTMLTokenTypes::StartTag);
    458438    ASSERT(hasName(token, baseTag));
     
    463443bool XSSAuditor::filterFormToken(HTMLToken& token)
    464444{
    465     ASSERT(m_state == Initial);
    466445    ASSERT(token.type() == HTMLTokenTypes::StartTag);
    467446    ASSERT(hasName(token, formTag));
     
    542521String XSSAuditor::decodedSnippetForAttribute(const HTMLToken& token, const HTMLToken::Attribute& attribute, AttributeKind treatment)
    543522{
    544     const size_t kMaximumSnippetLength = 100;
    545 
    546523    // The range doesn't inlcude the character which terminates the value. So,
    547524    // for an input of |name="value"|, the snippet is |name="value|. For an
     
    551528    int end = attribute.m_valueRange.m_end - token.startIndex();
    552529    String decodedSnippet = fullyDecodeString(snippetForRange(token, start, end), m_parser->document()->decoder());
    553     decodedSnippet.truncate(kMaximumSnippetLength);
     530    decodedSnippet.truncate(kMaximumFragmentLengthTarget);
    554531    if (treatment == SrcLikeAttribute) {
    555532        int slashCount;
     
    594571String XSSAuditor::snippetForJavaScript(const String& string)
    595572{
    596     const size_t kMaximumFragmentLengthTarget = 100;
    597 
    598573    size_t startPosition = 0;
    599574    size_t endPosition = string.length();
     
    604579        while (startPosition < endPosition && isHTMLSpace(string[startPosition]))
    605580            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 -->.
    606590        if (startsHTMLCommentAt(string, startPosition) || startsSingleLineCommentAt(string, startPosition)) {
    607591            while (startPosition < endPosition && !isJSNewline(string[startPosition]))
     
    616600    }
    617601
    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.
    626608    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            }
    634618        }
    635619        if (foundPosition > startPosition + kMaximumFragmentLengthTarget && isHTMLSpace(string[foundPosition])) {
     
    638622        }
    639623    }
    640    
     624
    641625    return string.substring(startPosition, endPosition - startPosition);
    642626}
  • trunk/Source/WebCore/html/parser/XSSAuditor.h

    r99096 r108551  
    4343
    4444private:
     45    static const size_t kMaximumFragmentLengthTarget = 100;
     46
    4547    enum State {
    4648        Uninitialized,
    47         Initial,
    48         AfterScriptStartTag,
     49        Initialized
    4950    };
    5051
     
    5657    void init();
    5758
    58     bool filterTokenInitial(HTMLToken&);
    59     bool filterTokenAfterScriptStartTag(HTMLToken&);
    60 
     59    bool filterStartToken(HTMLToken&);
     60    void filterEndToken(HTMLToken&);
     61    bool filterCharacterToken(HTMLToken&);
    6162    bool filterScriptToken(HTMLToken&);
    6263    bool filterObjectToken(HTMLToken&);
     
    8990    State m_state;
    9091    String m_cachedSnippet;
     92    bool m_shouldAllowCDATA;
     93    unsigned m_scriptTagNestingLevel;
    9194    bool m_notifiedClient;
    9295};
Note: See TracChangeset for help on using the changeset viewer.