• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008, 2009 Daniel Bates (dbates@intudata.com)
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include "config.h"
28 #include "XSSAuditor.h"
29 
30 #include <wtf/StdLibExtras.h>
31 #include <wtf/Vector.h>
32 
33 #include "Console.h"
34 #include "CString.h"
35 #include "DocumentLoader.h"
36 #include "DOMWindow.h"
37 #include "Frame.h"
38 #include "KURL.h"
39 #include "PreloadScanner.h"
40 #include "ResourceResponseBase.h"
41 #include "ScriptSourceCode.h"
42 #include "Settings.h"
43 #include "TextResourceDecoder.h"
44 
45 using namespace WTF;
46 
47 namespace WebCore {
48 
isNonCanonicalCharacter(UChar c)49 static bool isNonCanonicalCharacter(UChar c)
50 {
51     // Note, we don't remove backslashes like PHP stripslashes(), which among other things converts "\\0" to the \0 character.
52     // Instead, we remove backslashes and zeros (since the string "\\0" =(remove backslashes)=> "0"). However, this has the
53     // adverse effect that we remove any legitimate zeros from a string.
54     //
55     // For instance: new String("http://localhost:8000") => new String("http://localhost:8").
56     return (c == '\\' || c == '0' || c < ' ' || c == 127);
57 }
58 
XSSAuditor(Frame * frame)59 XSSAuditor::XSSAuditor(Frame* frame)
60     : m_frame(frame)
61 {
62 }
63 
~XSSAuditor()64 XSSAuditor::~XSSAuditor()
65 {
66 }
67 
isEnabled() const68 bool XSSAuditor::isEnabled() const
69 {
70     Settings* settings = m_frame->settings();
71     return (settings && settings->xssAuditorEnabled());
72 }
73 
canEvaluate(const String & code) const74 bool XSSAuditor::canEvaluate(const String& code) const
75 {
76     if (!isEnabled())
77         return true;
78 
79     if (findInRequest(code, false)) {
80         DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request.\n"));
81         m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String());
82         return false;
83     }
84     return true;
85 }
86 
canEvaluateJavaScriptURL(const String & code) const87 bool XSSAuditor::canEvaluateJavaScriptURL(const String& code) const
88 {
89     if (!isEnabled())
90         return true;
91 
92     if (findInRequest(code)) {
93         DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request.\n"));
94         m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String());
95         return false;
96     }
97     return true;
98 }
99 
canCreateInlineEventListener(const String &,const String & code) const100 bool XSSAuditor::canCreateInlineEventListener(const String&, const String& code) const
101 {
102     if (!isEnabled())
103         return true;
104 
105     if (findInRequest(code)) {
106         DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request.\n"));
107         m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String());
108         return false;
109     }
110     return true;
111 }
112 
canLoadExternalScriptFromSrc(const String & context,const String & url) const113 bool XSSAuditor::canLoadExternalScriptFromSrc(const String& context, const String& url) const
114 {
115     if (!isEnabled())
116         return true;
117 
118     if (findInRequest(context + url)) {
119         DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request.\n"));
120         m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String());
121         return false;
122     }
123     return true;
124 }
125 
canLoadObject(const String & url) const126 bool XSSAuditor::canLoadObject(const String& url) const
127 {
128     if (!isEnabled())
129         return true;
130 
131     if (findInRequest(url)) {
132         DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request"));
133         m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String());
134         return false;
135     }
136     return true;
137 }
138 
canSetBaseElementURL(const String & url) const139 bool XSSAuditor::canSetBaseElementURL(const String& url) const
140 {
141     if (!isEnabled())
142         return true;
143 
144     KURL baseElementURL(m_frame->document()->url(), url);
145     if (m_frame->document()->url().host() != baseElementURL.host() && findInRequest(url)) {
146         DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request"));
147         m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String());
148         return false;
149     }
150     return true;
151 }
152 
canonicalize(const String & string)153 String XSSAuditor::canonicalize(const String& string)
154 {
155     String result = decodeHTMLEntities(string);
156     return result.removeCharacters(&isNonCanonicalCharacter);
157 }
158 
decodeURL(const String & string,const TextEncoding & encoding,bool decodeHTMLentities)159 String XSSAuditor::decodeURL(const String& string, const TextEncoding& encoding, bool decodeHTMLentities)
160 {
161     String result;
162     String url = string;
163 
164     url.replace('+', ' ');
165     result = decodeURLEscapeSequences(url);
166     String decodedResult = encoding.decode(result.utf8().data(), result.length());
167     if (!decodedResult.isEmpty())
168         result = decodedResult;
169     if (decodeHTMLentities)
170         result = decodeHTMLEntities(result);
171     return result;
172 }
173 
decodeHTMLEntities(const String & string,bool leaveUndecodableHTMLEntitiesUntouched)174 String XSSAuditor::decodeHTMLEntities(const String& string, bool leaveUndecodableHTMLEntitiesUntouched)
175 {
176     SegmentedString source(string);
177     SegmentedString sourceShadow;
178     Vector<UChar> result;
179 
180     while (!source.isEmpty()) {
181         UChar cc = *source;
182         source.advance();
183 
184         if (cc != '&') {
185             result.append(cc);
186             continue;
187         }
188 
189         if (leaveUndecodableHTMLEntitiesUntouched)
190             sourceShadow = source;
191         bool notEnoughCharacters = false;
192         unsigned entity = PreloadScanner::consumeEntity(source, notEnoughCharacters);
193         // We ignore notEnoughCharacters because we might as well use this loop
194         // to copy the remaining characters into |result|.
195 
196         if (entity > 0xFFFF) {
197             result.append(U16_LEAD(entity));
198             result.append(U16_TRAIL(entity));
199         } else if (entity && (!leaveUndecodableHTMLEntitiesUntouched || entity != 0xFFFD)){
200             result.append(entity);
201         } else {
202             result.append('&');
203             if (leaveUndecodableHTMLEntitiesUntouched)
204                 source = sourceShadow;
205         }
206     }
207 
208     return String::adopt(result);
209 }
210 
findInRequest(const String & string,bool decodeHTMLentities) const211 bool XSSAuditor::findInRequest(const String& string, bool decodeHTMLentities) const
212 {
213     bool result = false;
214     Frame* parentFrame = m_frame->tree()->parent();
215     if (parentFrame && m_frame->document()->url() == blankURL())
216         result = findInRequest(parentFrame, string, decodeHTMLentities);
217     if (!result)
218         result = findInRequest(m_frame, string, decodeHTMLentities);
219     return result;
220 }
221 
findInRequest(Frame * frame,const String & string,bool decodeHTMLentities) const222 bool XSSAuditor::findInRequest(Frame* frame, const String& string, bool decodeHTMLentities) const
223 {
224     ASSERT(frame->document());
225     String pageURL = frame->document()->url().string();
226 
227     if (!frame->document()->decoder()) {
228         // Note, JavaScript URLs do not have a charset.
229         return false;
230     }
231 
232     if (protocolIs(pageURL, "data"))
233         return false;
234 
235     if (string.isEmpty())
236         return false;
237 
238     String canonicalizedString = canonicalize(string);
239     if (canonicalizedString.isEmpty())
240         return false;
241 
242     if (string.length() < pageURL.length()) {
243         // The string can actually fit inside the pageURL.
244         String decodedPageURL = canonicalize(decodeURL(pageURL, frame->document()->decoder()->encoding(), decodeHTMLentities));
245         if (decodedPageURL.find(canonicalizedString, 0, false) != -1)
246            return true;  // We've found the smoking gun.
247     }
248 
249     FormData* formDataObj = frame->loader()->documentLoader()->originalRequest().httpBody();
250     if (formDataObj && !formDataObj->isEmpty()) {
251         String formData = formDataObj->flattenToString();
252         if (string.length() < formData.length()) {
253             // Notice it is sufficient to compare the length of the string to
254             // the url-encoded POST data because the length of the url-decoded
255             // code is less than or equal to the length of the url-encoded
256             // string.
257             String decodedFormData = canonicalize(decodeURL(formData, frame->document()->decoder()->encoding(), decodeHTMLentities));
258             if (decodedFormData.find(canonicalizedString, 0, false) != -1)
259                 return true;  // We found the string in the POST data.
260         }
261     }
262 
263     return false;
264 }
265 
266 } // namespace WebCore
267 
268