diff options
Diffstat (limited to 'Source/WebCore/page/XSSAuditor.cpp')
-rw-r--r-- | Source/WebCore/page/XSSAuditor.cpp | 432 |
1 files changed, 0 insertions, 432 deletions
diff --git a/Source/WebCore/page/XSSAuditor.cpp b/Source/WebCore/page/XSSAuditor.cpp deleted file mode 100644 index 1b0e83f..0000000 --- a/Source/WebCore/page/XSSAuditor.cpp +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright (C) 2008, 2009 Daniel Bates (dbates@intudata.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "config.h" -#include "XSSAuditor.h" - -#include <wtf/StdLibExtras.h> -#include <wtf/Vector.h> - -#include "Console.h" -#include "DocumentLoader.h" -#include "DOMWindow.h" -#include "Frame.h" -#include "HTMLEntityParser.h" -#include "KURL.h" -#include "ResourceResponseBase.h" -#include "ScriptSourceCode.h" -#include "Settings.h" -#include "TextResourceDecoder.h" -#include <wtf/text/CString.h> -#include <wtf/text/StringConcatenate.h> - -namespace WebCore { - -static bool isNonCanonicalCharacter(UChar c) -{ - // We remove all non-ASCII characters, including non-printable ASCII characters. - // - // Note, we don't remove backslashes like PHP stripslashes(), which among other things converts "\\0" to the \0 character. - // Instead, we remove backslashes and zeros (since the string "\\0" =(remove backslashes)=> "0"). However, this has the - // adverse effect that we remove any legitimate zeros from a string. - // - // For instance: new String("http://localhost:8000") => new String("http://localhost:8"). - return (c == '\\' || c == '0' || c < ' ' || c >= 127); -} - -static bool isIllegalURICharacter(UChar c) -{ - // The characters described in section 2.4.3 of RFC 2396 <http://www.faqs.org/rfcs/rfc2396.html> in addition to the - // single quote character "'" are considered illegal URI characters. That is, the following characters cannot appear - // in a valid URI: ', ", <, > - // - // If the request does not contain these characters then we can assume that no inline scripts have been injected - // into the response page, because it is impossible to write an inline script of the form <script>...</script> - // without "<", ">". - return (c == '\'' || c == '"' || c == '<' || c == '>'); -} - -String XSSAuditor::CachingURLCanonicalizer::canonicalizeURL(FormData* formData, const TextEncoding& encoding, bool decodeEntities, - bool decodeURLEscapeSequencesTwice) -{ - if (decodeEntities == m_decodeEntities && decodeURLEscapeSequencesTwice == m_decodeURLEscapeSequencesTwice - && encoding == m_encoding && formData == m_formData) - return m_cachedCanonicalizedURL; - m_formData = formData; - return canonicalizeURL(formData->flattenToString(), encoding, decodeEntities, decodeURLEscapeSequencesTwice); -} - -String XSSAuditor::CachingURLCanonicalizer::canonicalizeURL(const String& url, const TextEncoding& encoding, bool decodeEntities, - bool decodeURLEscapeSequencesTwice) -{ - if (decodeEntities == m_decodeEntities && decodeURLEscapeSequencesTwice == m_decodeURLEscapeSequencesTwice - && encoding == m_encoding && url == m_inputURL) - return m_cachedCanonicalizedURL; - - m_cachedCanonicalizedURL = canonicalize(decodeURL(url, encoding, decodeEntities, decodeURLEscapeSequencesTwice)); - m_inputURL = url; - m_encoding = encoding; - m_decodeEntities = decodeEntities; - m_decodeURLEscapeSequencesTwice = decodeURLEscapeSequencesTwice; - ++m_generation; - return m_cachedCanonicalizedURL; -} - -void XSSAuditor::CachingURLCanonicalizer::clear() -{ - m_formData.clear(); - m_inputURL = String(); -} - -XSSAuditor::XSSAuditor(Frame* frame) - : m_frame(frame) - , m_generationOfSuffixTree(-1) -{ -} - -XSSAuditor::~XSSAuditor() -{ -} - -bool XSSAuditor::isEnabled() const -{ - Settings* settings = m_frame->settings(); - return (settings && settings->xssAuditorEnabled()); -} - -bool XSSAuditor::canEvaluate(const String& code) const -{ - if (!isEnabled()) - return true; - - FindTask task; - task.string = code; - task.decodeEntities = false; - task.allowRequestIfNoIllegalURICharacters = true; - - if (findInRequest(task)) { - DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request.\n")); - m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String()); - return false; - } - return true; -} - -bool XSSAuditor::canEvaluateJavaScriptURL(const String& code) const -{ - if (!isEnabled()) - return true; - - FindTask task; - task.string = code; - task.decodeURLEscapeSequencesTwice = true; - - if (findInRequest(task)) { - DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request.\n")); - m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String()); - return false; - } - return true; -} - -bool XSSAuditor::canCreateInlineEventListener(const String&, const String& code) const -{ - if (!isEnabled()) - return true; - - FindTask task; - task.string = code; - task.allowRequestIfNoIllegalURICharacters = true; - - if (findInRequest(task)) { - DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request.\n")); - m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String()); - return false; - } - return true; -} - -bool XSSAuditor::canLoadExternalScriptFromSrc(const String& url) const -{ - if (!isEnabled()) - return true; - - if (isSameOriginResource(url)) - return true; - - FindTask task; - task.string = url; - task.allowRequestIfNoIllegalURICharacters = true; - - if (findInRequest(task)) { - DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request.\n")); - m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String()); - return false; - } - return true; -} - -bool XSSAuditor::canLoadObject(const String& url) const -{ - if (!isEnabled()) - return true; - - if (isSameOriginResource(url)) - return true; - - FindTask task; - task.string = url; - task.allowRequestIfNoIllegalURICharacters = true; - - if (findInRequest(task)) { - String consoleMessage = makeString("Refused to load an object. URL found within request: \"", url, "\".\n"); - m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String()); - return false; - } - return true; -} - -bool XSSAuditor::canSetBaseElementURL(const String& url) const -{ - if (!isEnabled()) - return true; - - if (isSameOriginResource(url)) - return true; - - FindTask task; - task.string = url; - task.allowRequestIfNoIllegalURICharacters = true; - - if (findInRequest(task)) { - DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to load from document base URL. URL found within request.\n")); - m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String()); - return false; - } - return true; -} - -String XSSAuditor::canonicalize(const String& string) -{ - String result = decodeHTMLEntities(string); - return result.removeCharacters(&isNonCanonicalCharacter); -} - -String XSSAuditor::decodeURL(const String& string, const TextEncoding& encoding, bool decodeEntities, bool decodeURLEscapeSequencesTwice) -{ - String result; - String url = string; - - url.replace('+', ' '); - result = decodeURLEscapeSequences(url); - CString utf8Url = result.utf8(); - String decodedResult = encoding.decode(utf8Url.data(), utf8Url.length()); - if (!decodedResult.isEmpty()) - result = decodedResult; - if (decodeURLEscapeSequencesTwice) { - result = decodeURLEscapeSequences(result); - utf8Url = result.utf8(); - decodedResult = encoding.decode(utf8Url.data(), utf8Url.length()); - if (!decodedResult.isEmpty()) - result = decodedResult; - } - if (decodeEntities) - result = decodeHTMLEntities(result); - return result; -} - -String XSSAuditor::decodeHTMLEntities(const String& string, bool leaveUndecodableEntitiesUntouched) -{ - SegmentedString source(string); - SegmentedString sourceShadow; - Vector<UChar> result; - - while (!source.isEmpty()) { - UChar cc = *source; - source.advance(); - - if (cc != '&') { - result.append(cc); - continue; - } - - if (leaveUndecodableEntitiesUntouched) - sourceShadow = source; - bool notEnoughCharacters = false; - Vector<UChar, 16> decodedEntity; - bool success = consumeHTMLEntity(source, decodedEntity, notEnoughCharacters); - // We ignore notEnoughCharacters because we might as well use this loop - // to copy the remaining characters into |result|. - if (!success || (!leaveUndecodableEntitiesUntouched && decodedEntity.size() == 1 && decodedEntity[0] == 0xFFFD)) { - result.append('&'); - if (leaveUndecodableEntitiesUntouched) - source = sourceShadow; - } else { - Vector<UChar>::const_iterator iter = decodedEntity.begin(); - for (; iter != decodedEntity.end(); ++iter) - result.append(*iter); - } - } - - return String::adopt(result); -} - -bool XSSAuditor::isSameOriginResource(const String& url) const -{ - // 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_frame->document()->url(), url); - return (m_frame->document()->url().host() == resourceURL.host() && resourceURL.query().isEmpty()); -} - -XSSProtectionDisposition XSSAuditor::xssProtection() const -{ - DEFINE_STATIC_LOCAL(String, XSSProtectionHeader, ("X-XSS-Protection")); - - Frame* frame = m_frame; - if (frame->document()->url() == blankURL()) - frame = m_frame->tree()->parent(); - - return parseXSSProtectionHeader(frame->loader()->documentLoader()->response().httpHeaderField(XSSProtectionHeader)); -} - -bool XSSAuditor::findInRequest(const FindTask& task) const -{ - bool result = false; - Frame* parentFrame = m_frame->tree()->parent(); - Frame* blockFrame = parentFrame; - if (parentFrame && m_frame->document()->url() == blankURL()) - result = findInRequest(parentFrame, task); - if (!result) { - result = findInRequest(m_frame, task); - blockFrame = m_frame; - } - if (!result) - return false; - - switch (xssProtection()) { - case XSSProtectionDisabled: - return false; - case XSSProtectionEnabled: - break; - case XSSProtectionBlockEnabled: - if (blockFrame) { - blockFrame->loader()->stopAllLoaders(); - blockFrame->navigationScheduler()->scheduleLocationChange(blockFrame->document()->securityOrigin(), blankURL(), String()); - } - break; - default: - ASSERT_NOT_REACHED(); - } - return true; -} - -bool XSSAuditor::findInRequest(Frame* frame, const FindTask& task) const -{ - ASSERT(frame->document()); - - if (!frame->document()->decoder()) { - // Note, JavaScript URLs do not have a charset. - return false; - } - - if (task.string.isEmpty()) - return false; - - DocumentLoader *documentLoader = frame->loader()->documentLoader(); - if (!documentLoader) - return false; - - FormData* formDataObj = documentLoader->originalRequest().httpBody(); - const bool hasFormData = formDataObj && !formDataObj->isEmpty(); - String pageURL = frame->document()->url().string(); - - if (!hasFormData) { - // We clear out our form data caches, in case we're holding onto a bunch of memory. - m_formDataCache.clear(); - m_formDataSuffixTree.clear(); - } - - String canonicalizedString; - if (!hasFormData && task.string.length() > 2 * pageURL.length()) { - // Q: Why do we bother to do this check at all? - // A: Canonicalizing large inline scripts can be expensive. We want to - // reduce the size of the string before we call canonicalize below, - // since it could result in an unneeded allocation and memcpy. - // - // Q: Why do we multiply by two here? - // A: We attempt to detect reflected XSS even when the server - // transforms the attacker's input with addSlashes. The best the - // attacker can do get the server to inflate his/her input by a - // factor of two by sending " characters, which the server - // transforms to \". - canonicalizedString = task.string.substring(0, 2 * pageURL.length()); - } else - canonicalizedString = task.string; - - if (frame->document()->url().protocolIsData()) - return false; - - canonicalizedString = canonicalize(canonicalizedString); - if (canonicalizedString.isEmpty()) - return false; - - if (!task.context.isEmpty()) - canonicalizedString = task.context + canonicalizedString; - - String decodedPageURL = m_pageURLCache.canonicalizeURL(pageURL, frame->document()->decoder()->encoding(), task.decodeEntities, task.decodeURLEscapeSequencesTwice); - - if (task.allowRequestIfNoIllegalURICharacters && !hasFormData && decodedPageURL.find(&isIllegalURICharacter, 0) == notFound) - return false; // Injection is impossible because the request does not contain any illegal URI characters. - - if (decodedPageURL.find(canonicalizedString, 0, false) != notFound) - return true; // We've found the string in the GET data. - - if (hasFormData) { - String decodedFormData = m_formDataCache.canonicalizeURL(formDataObj, frame->document()->decoder()->encoding(), task.decodeEntities, task.decodeURLEscapeSequencesTwice); - - if (m_generationOfSuffixTree != m_formDataCache.generation()) { - m_formDataSuffixTree = new SuffixTree<ASCIICodebook>(decodedFormData, 5); - m_generationOfSuffixTree = m_formDataCache.generation(); - } - - // Try a fast-reject via the suffixTree. - if (m_formDataSuffixTree && !m_formDataSuffixTree->mightContain(canonicalizedString)) - return false; - - if (decodedFormData.find(canonicalizedString, 0, false) != notFound) - return true; // We found the string in the POST data. - } - - return false; -} - -} // namespace WebCore - |