| Index: Source/WebCore/html/parser/XSSAuditor.cpp
|
| ===================================================================
|
| --- Source/WebCore/html/parser/XSSAuditor.cpp (revision 110728)
|
| +++ Source/WebCore/html/parser/XSSAuditor.cpp (working copy)
|
| @@ -277,7 +277,7 @@
|
| case AfterScriptStartTag:
|
| didBlockScript = filterTokenAfterScriptStartTag(token);
|
| ASSERT(m_state == Initial);
|
| - m_cachedSnippet = String();
|
| + m_cachedDecodedSnippet = String();
|
| break;
|
| }
|
|
|
| @@ -341,16 +341,10 @@
|
| return false;
|
| }
|
|
|
| - TextResourceDecoder* decoder = m_parser->document()->decoder();
|
| - if (isContainedInRequest(fullyDecodeString(m_cachedSnippet, decoder))) {
|
| - int start = 0;
|
| - int end = token.endIndex() - token.startIndex();
|
| - String snippet = snippetForJavaScript(snippetForRange(token, start, end));
|
| - if (isContainedInRequest(fullyDecodeString(snippet, decoder))) {
|
| - token.eraseCharacters();
|
| - token.appendToCharacter(' '); // Technically, character tokens can't be empty.
|
| - return true;
|
| - }
|
| + if (isContainedInRequest(m_cachedDecodedSnippet) && isContainedInRequest(decodedSnippetForJavaScript(token))) {
|
| + token.eraseCharacters();
|
| + token.appendToCharacter(' '); // Technically, character tokens can't be empty.
|
| + return true;
|
| }
|
| return false;
|
| }
|
| @@ -361,11 +355,12 @@
|
| ASSERT(token.type() == HTMLTokenTypes::StartTag);
|
| ASSERT(hasName(token, scriptTag));
|
|
|
| - if (eraseAttributeIfInjected(token, srcAttr, blankURL().string(), SrcLikeAttribute))
|
| - return true;
|
| + m_state = AfterScriptStartTag;
|
| + m_cachedDecodedSnippet = stripLeadingAndTrailingHTMLSpaces(decodedSnippetForToken(token));
|
|
|
| - m_state = AfterScriptStartTag;
|
| - m_cachedSnippet = m_parser->sourceForToken(token);
|
| + if (isContainedInRequest(decodedSnippetForName(token)))
|
| + return eraseAttributeIfInjected(token, srcAttr, blankURL().string(), SrcLikeAttribute);
|
| +
|
| return false;
|
| }
|
|
|
| @@ -376,11 +371,11 @@
|
| ASSERT(hasName(token, objectTag));
|
|
|
| bool didBlockScript = false;
|
| -
|
| - didBlockScript |= eraseAttributeIfInjected(token, dataAttr, blankURL().string(), SrcLikeAttribute);
|
| - didBlockScript |= eraseAttributeIfInjected(token, typeAttr);
|
| - didBlockScript |= eraseAttributeIfInjected(token, classidAttr);
|
| -
|
| + if (isContainedInRequest(decodedSnippetForName(token))) {
|
| + didBlockScript |= eraseAttributeIfInjected(token, dataAttr, blankURL().string(), SrcLikeAttribute);
|
| + didBlockScript |= eraseAttributeIfInjected(token, typeAttr);
|
| + didBlockScript |= eraseAttributeIfInjected(token, classidAttr);
|
| + }
|
| return didBlockScript;
|
| }
|
|
|
| @@ -410,11 +405,11 @@
|
| ASSERT(hasName(token, embedTag));
|
|
|
| bool didBlockScript = false;
|
| -
|
| - didBlockScript |= eraseAttributeIfInjected(token, codeAttr, String(), SrcLikeAttribute);
|
| - didBlockScript |= eraseAttributeIfInjected(token, srcAttr, blankURL().string(), SrcLikeAttribute);
|
| - didBlockScript |= eraseAttributeIfInjected(token, typeAttr);
|
| -
|
| + if (isContainedInRequest(decodedSnippetForName(token))) {
|
| + didBlockScript |= eraseAttributeIfInjected(token, codeAttr, String(), SrcLikeAttribute);
|
| + didBlockScript |= eraseAttributeIfInjected(token, srcAttr, blankURL().string(), SrcLikeAttribute);
|
| + didBlockScript |= eraseAttributeIfInjected(token, typeAttr);
|
| + }
|
| return didBlockScript;
|
| }
|
|
|
| @@ -425,10 +420,10 @@
|
| ASSERT(hasName(token, appletTag));
|
|
|
| bool didBlockScript = false;
|
| -
|
| - didBlockScript |= eraseAttributeIfInjected(token, codeAttr, String(), SrcLikeAttribute);
|
| - didBlockScript |= eraseAttributeIfInjected(token, objectAttr);
|
| -
|
| + if (isContainedInRequest(decodedSnippetForName(token))) {
|
| + didBlockScript |= eraseAttributeIfInjected(token, codeAttr, String(), SrcLikeAttribute);
|
| + didBlockScript |= eraseAttributeIfInjected(token, objectAttr);
|
| + }
|
| return didBlockScript;
|
| }
|
|
|
| @@ -438,7 +433,10 @@
|
| ASSERT(token.type() == HTMLTokenTypes::StartTag);
|
| ASSERT(hasName(token, iframeTag));
|
|
|
| - return eraseAttributeIfInjected(token, srcAttr, String(), SrcLikeAttribute);
|
| + if (isContainedInRequest(decodedSnippetForName(token)))
|
| + return eraseAttributeIfInjected(token, srcAttr, String(), SrcLikeAttribute);
|
| +
|
| + return false;
|
| }
|
|
|
| bool XSSAuditor::filterMetaToken(HTMLToken& token)
|
| @@ -531,13 +529,19 @@
|
| return false;
|
| }
|
|
|
| -String XSSAuditor::snippetForRange(const HTMLToken& token, int start, int end)
|
| +String XSSAuditor::decodedSnippetForToken(const HTMLToken& token)
|
| {
|
| - // FIXME: There's an extra allocation here that we could save by
|
| - // passing the range to the parser.
|
| - return m_parser->sourceForToken(token).substring(start, end - start);
|
| + String snippet = m_parser->sourceForToken(token);
|
| + return fullyDecodeString(snippet, m_parser->document()->decoder());
|
| }
|
|
|
| +String XSSAuditor::decodedSnippetForName(const HTMLToken& token)
|
| +{
|
| + // Grab a fixed number of characters equal to the length of the token's
|
| + // name plus one (to account for the "<").
|
| + return decodedSnippetForToken(token).substring(0, token.name().size() + 1);
|
| +}
|
| +
|
| String XSSAuditor::decodedSnippetForAttribute(const HTMLToken& token, const HTMLToken::Attribute& attribute, AttributeKind treatment)
|
| {
|
| const size_t kMaximumSnippetLength = 100;
|
| @@ -548,7 +552,7 @@
|
| // FIXME: We should grab one character before the name also.
|
| int start = attribute.m_nameRange.m_start - token.startIndex();
|
| int end = attribute.m_valueRange.m_end - token.startIndex();
|
| - String decodedSnippet = fullyDecodeString(snippetForRange(token, start, end), m_parser->document()->decoder());
|
| + String decodedSnippet = fullyDecodeString(m_parser->sourceForToken(token).substring(start, end - start), m_parser->document()->decoder());
|
| decodedSnippet.truncate(kMaximumSnippetLength);
|
| if (treatment == SrcLikeAttribute) {
|
| int slashCount;
|
| @@ -567,31 +571,9 @@
|
| return decodedSnippet;
|
| }
|
|
|
| -bool XSSAuditor::isContainedInRequest(const String& decodedSnippet)
|
| +String XSSAuditor::decodedSnippetForJavaScript(const HTMLToken& token)
|
| {
|
| - if (decodedSnippet.isEmpty())
|
| - return false;
|
| - if (m_decodedURL.find(decodedSnippet, 0, false) != notFound)
|
| - return true;
|
| - if (m_decodedHTTPBodySuffixTree && !m_decodedHTTPBodySuffixTree->mightContain(decodedSnippet))
|
| - return false;
|
| - return m_decodedHTTPBody.find(decodedSnippet, 0, false) != notFound;
|
| -}
|
| -
|
| -bool XSSAuditor::isSameOriginResource(const String& url)
|
| -{
|
| - // If the resource is loaded from the same URL as the enclosing page, it's
|
| - // probably not an XSS attack, so we reduce false positives by allowing the
|
| - // request. If the resource has a query string, we're more suspicious,
|
| - // however, because that's pretty rare and the attacker might be able to
|
| - // trick a server-side script into doing something dangerous with the query
|
| - // string.
|
| - KURL resourceURL(m_parser->document()->url(), url);
|
| - return (m_parser->document()->url().host() == resourceURL.host() && resourceURL.query().isEmpty());
|
| -}
|
| -
|
| -String XSSAuditor::snippetForJavaScript(const String& string)
|
| -{
|
| + String string = m_parser->sourceForToken(token);
|
| const size_t kMaximumFragmentLengthTarget = 100;
|
|
|
| size_t startPosition = 0;
|
| @@ -637,7 +619,30 @@
|
| }
|
| }
|
|
|
| - return string.substring(startPosition, endPosition - startPosition);
|
| + return fullyDecodeString(string.substring(startPosition, endPosition - startPosition), m_parser->document()->decoder());
|
| }
|
|
|
| +bool XSSAuditor::isContainedInRequest(const String& decodedSnippet)
|
| +{
|
| + if (decodedSnippet.isEmpty())
|
| + return false;
|
| + if (m_decodedURL.find(decodedSnippet, 0, false) != notFound)
|
| + return true;
|
| + if (m_decodedHTTPBodySuffixTree && !m_decodedHTTPBodySuffixTree->mightContain(decodedSnippet))
|
| + return false;
|
| + return m_decodedHTTPBody.find(decodedSnippet, 0, false) != notFound;
|
| +}
|
| +
|
| +bool XSSAuditor::isSameOriginResource(const String& url)
|
| +{
|
| + // If the resource is loaded from the same URL as the enclosing page, it's
|
| + // probably not an XSS attack, so we reduce false positives by allowing the
|
| + // request. If the resource has a query string, we're more suspicious,
|
| + // however, because that's pretty rare and the attacker might be able to
|
| + // trick a server-side script into doing something dangerous with the query
|
| + // string.
|
| + KURL resourceURL(m_parser->document()->url(), url);
|
| + return (m_parser->document()->url().host() == resourceURL.host() && resourceURL.query().isEmpty());
|
| +}
|
| +
|
| } // namespace WebCore
|
|
|