diff options
author | Ben Murdoch <benm@google.com> | 2009-08-11 17:01:47 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2009-08-11 18:21:02 +0100 |
commit | 0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5 (patch) | |
tree | 2943df35f62d885c89d01063cc528dd73b480fea /WebCore/page/XSSAuditor.cpp | |
parent | 7e7a70bfa49a1122b2597a1e6367d89eb4035eca (diff) | |
download | external_webkit-0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5.zip external_webkit-0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5.tar.gz external_webkit-0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5.tar.bz2 |
Merge in WebKit r47029.
Diffstat (limited to 'WebCore/page/XSSAuditor.cpp')
-rw-r--r-- | WebCore/page/XSSAuditor.cpp | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/WebCore/page/XSSAuditor.cpp b/WebCore/page/XSSAuditor.cpp new file mode 100644 index 0000000..70b691b --- /dev/null +++ b/WebCore/page/XSSAuditor.cpp @@ -0,0 +1,267 @@ +/* + * 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 "CString.h" +#include "DocumentLoader.h" +#include "DOMWindow.h" +#include "Frame.h" +#include "KURL.h" +#include "PreloadScanner.h" +#include "ResourceResponseBase.h" +#include "ScriptSourceCode.h" +#include "Settings.h" +#include "TextResourceDecoder.h" + +using namespace WTF; + +namespace WebCore { + +static bool isNonCanonicalCharacter(UChar c) +{ + // 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); +} + +XSSAuditor::XSSAuditor(Frame* frame) + : m_frame(frame) +{ +} + +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; + + if (findInRequest(code, false)) { + 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; + + if (findInRequest(code)) { + 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; + + if (findInRequest(code)) { + 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& context, const String& url) const +{ + if (!isEnabled()) + return true; + + if (findInRequest(context + url)) { + 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 (findInRequest(url)) { + DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request")); + 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; + + KURL baseElementURL(m_frame->document()->url(), url); + if (m_frame->document()->url().host() != baseElementURL.host() && findInRequest(url)) { + DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request")); + 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 decodeHTMLentities) +{ + String result; + String url = string; + + url.replace('+', ' '); + result = decodeURLEscapeSequences(url); + String decodedResult = encoding.decode(result.utf8().data(), result.length()); + if (!decodedResult.isEmpty()) + result = decodedResult; + if (decodeHTMLentities) + result = decodeHTMLEntities(result); + return result; +} + +String XSSAuditor::decodeHTMLEntities(const String& string, bool leaveUndecodableHTMLEntitiesUntouched) +{ + SegmentedString source(string); + SegmentedString sourceShadow; + Vector<UChar> result; + + while (!source.isEmpty()) { + UChar cc = *source; + source.advance(); + + if (cc != '&') { + result.append(cc); + continue; + } + + if (leaveUndecodableHTMLEntitiesUntouched) + sourceShadow = source; + bool notEnoughCharacters = false; + unsigned entity = PreloadScanner::consumeEntity(source, notEnoughCharacters); + // We ignore notEnoughCharacters because we might as well use this loop + // to copy the remaining characters into |result|. + + if (entity > 0xFFFF) { + result.append(U16_LEAD(entity)); + result.append(U16_TRAIL(entity)); + } else if (entity && (!leaveUndecodableHTMLEntitiesUntouched || entity != 0xFFFD)){ + result.append(entity); + } else { + result.append('&'); + if (leaveUndecodableHTMLEntitiesUntouched) + source = sourceShadow; + } + } + + return String::adopt(result); +} + +bool XSSAuditor::findInRequest(const String& string, bool decodeHTMLentities) const +{ + bool result = false; + Frame* parentFrame = m_frame->tree()->parent(); + if (parentFrame && m_frame->document()->url() == blankURL()) + result = findInRequest(parentFrame, string, decodeHTMLentities); + if (!result) + result = findInRequest(m_frame, string, decodeHTMLentities); + return result; +} + +bool XSSAuditor::findInRequest(Frame* frame, const String& string, bool decodeHTMLentities) const +{ + ASSERT(frame->document()); + String pageURL = frame->document()->url().string(); + + if (!frame->document()->decoder()) { + // Note, JavaScript URLs do not have a charset. + return false; + } + + if (protocolIs(pageURL, "data")) + return false; + + if (string.isEmpty()) + return false; + + String canonicalizedString = canonicalize(string); + if (canonicalizedString.isEmpty()) + return false; + + if (string.length() < pageURL.length()) { + // The string can actually fit inside the pageURL. + String decodedPageURL = canonicalize(decodeURL(pageURL, frame->document()->decoder()->encoding(), decodeHTMLentities)); + if (decodedPageURL.find(canonicalizedString, 0, false) != -1) + return true; // We've found the smoking gun. + } + + FormData* formDataObj = frame->loader()->documentLoader()->originalRequest().httpBody(); + if (formDataObj && !formDataObj->isEmpty()) { + String formData = formDataObj->flattenToString(); + if (string.length() < formData.length()) { + // Notice it is sufficient to compare the length of the string to + // the url-encoded POST data because the length of the url-decoded + // code is less than or equal to the length of the url-encoded + // string. + String decodedFormData = canonicalize(decodeURL(formData, frame->document()->decoder()->encoding(), decodeHTMLentities)); + if (decodedFormData.find(canonicalizedString, 0, false) != -1) + return true; // We found the string in the POST data. + } + } + + return false; +} + +} // namespace WebCore + |