1 /*
2 * Copyright (C) 2012 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
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 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "config.h"
30 #include "core/loader/MixedContentChecker.h"
31
32 #include "core/dom/Document.h"
33 #include "core/frame/LocalFrame.h"
34 #include "core/frame/Settings.h"
35 #include "core/frame/UseCounter.h"
36 #include "core/inspector/ConsoleMessage.h"
37 #include "core/loader/DocumentLoader.h"
38 #include "core/loader/FrameLoader.h"
39 #include "core/loader/FrameLoaderClient.h"
40 #include "platform/RuntimeEnabledFeatures.h"
41 #include "platform/weborigin/SchemeRegistry.h"
42 #include "platform/weborigin/SecurityOrigin.h"
43 #include "public/platform/Platform.h"
44 #include "wtf/text/StringBuilder.h"
45
46 namespace blink {
47
48 namespace {
49 } // namespace
50
MixedContentChecker(LocalFrame * frame)51 MixedContentChecker::MixedContentChecker(LocalFrame* frame)
52 : m_frame(frame)
53 {
54 }
55
client() const56 FrameLoaderClient* MixedContentChecker::client() const
57 {
58 return m_frame->loader().client();
59 }
60
61 // static
isMixedContent(SecurityOrigin * securityOrigin,const KURL & url)62 bool MixedContentChecker::isMixedContent(SecurityOrigin* securityOrigin, const KURL& url)
63 {
64 if (securityOrigin->protocol() != "https")
65 return false; // We only care about HTTPS security origins.
66
67 // We're in a secure context, so |url| is mixed content if it's insecure.
68 return !SecurityOrigin::isSecure(url);
69 }
70
71 // static
contextTypeFromContext(WebURLRequest::RequestContext context)72 MixedContentChecker::ContextType MixedContentChecker::contextTypeFromContext(WebURLRequest::RequestContext context)
73 {
74 switch (context) {
75 // "Optionally-blockable" mixed content
76 case WebURLRequest::RequestContextAudio:
77 case WebURLRequest::RequestContextFavicon:
78 case WebURLRequest::RequestContextImage:
79 case WebURLRequest::RequestContextVideo:
80 return ContextTypeOptionallyBlockable;
81
82 // "Blockable" mixed content
83 case WebURLRequest::RequestContextBeacon:
84 case WebURLRequest::RequestContextCSPReport:
85 case WebURLRequest::RequestContextEmbed:
86 case WebURLRequest::RequestContextFetch:
87 case WebURLRequest::RequestContextFont:
88 case WebURLRequest::RequestContextForm:
89 case WebURLRequest::RequestContextFrame:
90 case WebURLRequest::RequestContextHyperlink:
91 case WebURLRequest::RequestContextIframe:
92 case WebURLRequest::RequestContextImageSet:
93 case WebURLRequest::RequestContextImport:
94 case WebURLRequest::RequestContextLocation:
95 case WebURLRequest::RequestContextManifest:
96 case WebURLRequest::RequestContextObject:
97 case WebURLRequest::RequestContextPing:
98 case WebURLRequest::RequestContextScript:
99 case WebURLRequest::RequestContextServiceWorker:
100 case WebURLRequest::RequestContextSharedWorker:
101 case WebURLRequest::RequestContextStyle:
102 case WebURLRequest::RequestContextSubresource:
103 case WebURLRequest::RequestContextTrack:
104 case WebURLRequest::RequestContextWorker:
105 case WebURLRequest::RequestContextXSLT:
106 return ContextTypeBlockable;
107
108 // "Blockable" mixed content whose behavior changed recently, and which is thus guarded behind the "lax" flag
109 case WebURLRequest::RequestContextEventSource:
110 case WebURLRequest::RequestContextXMLHttpRequest:
111 return ContextTypeBlockableUnlessLax;
112
113 // FIXME: Contexts that we should block, but don't currently. https://crbug.com/388650
114 case WebURLRequest::RequestContextDownload:
115 case WebURLRequest::RequestContextInternal:
116 case WebURLRequest::RequestContextPlugin:
117 case WebURLRequest::RequestContextPrefetch:
118 return ContextTypeShouldBeBlockable;
119
120 case WebURLRequest::RequestContextUnspecified:
121 ASSERT_NOT_REACHED();
122 }
123 ASSERT_NOT_REACHED();
124 return ContextTypeBlockable;
125 }
126
127 // static
typeNameFromContext(WebURLRequest::RequestContext context)128 const char* MixedContentChecker::typeNameFromContext(WebURLRequest::RequestContext context)
129 {
130 switch (context) {
131 case WebURLRequest::RequestContextAudio:
132 return "audio file";
133 case WebURLRequest::RequestContextBeacon:
134 return "Beacon endpoint";
135 case WebURLRequest::RequestContextCSPReport:
136 return "Content Security Policy reporting endpoint";
137 case WebURLRequest::RequestContextDownload:
138 return "download";
139 case WebURLRequest::RequestContextEmbed:
140 return "plugin resource";
141 case WebURLRequest::RequestContextEventSource:
142 return "EventSource endpoint";
143 case WebURLRequest::RequestContextFavicon:
144 return "favicon";
145 case WebURLRequest::RequestContextFetch:
146 return "resource";
147 case WebURLRequest::RequestContextFont:
148 return "font";
149 case WebURLRequest::RequestContextForm:
150 return "form action";
151 case WebURLRequest::RequestContextFrame:
152 return "frame";
153 case WebURLRequest::RequestContextHyperlink:
154 return "resource";
155 case WebURLRequest::RequestContextIframe:
156 return "frame";
157 case WebURLRequest::RequestContextImage:
158 return "image";
159 case WebURLRequest::RequestContextImageSet:
160 return "image";
161 case WebURLRequest::RequestContextImport:
162 return "HTML Import";
163 case WebURLRequest::RequestContextInternal:
164 return "resource";
165 case WebURLRequest::RequestContextLocation:
166 return "resource";
167 case WebURLRequest::RequestContextManifest:
168 return "manifest";
169 case WebURLRequest::RequestContextObject:
170 return "plugin resource";
171 case WebURLRequest::RequestContextPing:
172 return "hyperlink auditing endpoint";
173 case WebURLRequest::RequestContextPlugin:
174 return "plugin data";
175 case WebURLRequest::RequestContextPrefetch:
176 return "prefetch resource";
177 case WebURLRequest::RequestContextScript:
178 return "script";
179 case WebURLRequest::RequestContextServiceWorker:
180 return "Service Worker script";
181 case WebURLRequest::RequestContextSharedWorker:
182 return "Shared Worker script";
183 case WebURLRequest::RequestContextStyle:
184 return "stylesheet";
185 case WebURLRequest::RequestContextSubresource:
186 return "resource";
187 case WebURLRequest::RequestContextTrack:
188 return "Text Track";
189 case WebURLRequest::RequestContextUnspecified:
190 return "resource";
191 case WebURLRequest::RequestContextVideo:
192 return "video";
193 case WebURLRequest::RequestContextWorker:
194 return "Worker script";
195 case WebURLRequest::RequestContextXMLHttpRequest:
196 return "XMLHttpRequest endpoint";
197 case WebURLRequest::RequestContextXSLT:
198 return "XSLT";
199 }
200 ASSERT_NOT_REACHED();
201 return "resource";
202 }
203
204 // static
logToConsole(LocalFrame * frame,const KURL & url,WebURLRequest::RequestContext requestContext,bool allowed)205 void MixedContentChecker::logToConsole(LocalFrame* frame, const KURL& url, WebURLRequest::RequestContext requestContext, bool allowed)
206 {
207 String message = String::format(
208 "Mixed Content: The page at '%s' was loaded over HTTPS, but requested an insecure %s '%s'. %s",
209 frame->document()->url().elidedString().utf8().data(), typeNameFromContext(requestContext), url.elidedString().utf8().data(),
210 allowed ? "This content should also be served over HTTPS." : "This request has been blocked; the content must be served over HTTPS.");
211 MessageLevel messageLevel = allowed ? WarningMessageLevel : ErrorMessageLevel;
212 frame->document()->addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, messageLevel, message));
213 }
214
215 // static
shouldBlockFetch(LocalFrame * frame,const ResourceRequest & resourceRequest,const KURL & url)216 bool MixedContentChecker::shouldBlockFetch(LocalFrame* frame, const ResourceRequest& resourceRequest, const KURL& url)
217 {
218 // No frame, no mixed content:
219 if (!frame)
220 return false;
221
222 // Check the top frame first.
223 if (Frame* top = frame->tree().top()) {
224 // FIXME: We need a way to access the top-level frame's SecurityOrigin when that frame
225 // is in a different process from the current frame. Until that is done, we bail out
226 // early and allow the load.
227 if (!top->isLocalFrame())
228 return false;
229
230 LocalFrame* localTop = toLocalFrame(top);
231 if (frame != localTop && shouldBlockFetch(localTop, resourceRequest, url))
232 return true;
233 }
234
235 // We only care about subresource loads; top-level navigations cannot be mixed content.
236 if (resourceRequest.frameType() == WebURLRequest::FrameTypeTopLevel)
237 return false;
238
239 // No mixed content, no problem.
240 if (!isMixedContent(frame->document()->securityOrigin(), url))
241 return false;
242
243 Settings* settings = frame->settings();
244 FrameLoaderClient* client = frame->loader().client();
245 SecurityOrigin* securityOrigin = frame->document()->securityOrigin();
246 bool allowed = false;
247
248 ContextType contextType = contextTypeFromContext(resourceRequest.requestContext());
249 if (contextType == ContextTypeBlockableUnlessLax)
250 contextType = RuntimeEnabledFeatures::laxMixedContentCheckingEnabled() ? ContextTypeOptionallyBlockable : ContextTypeBlockable;
251
252 switch (contextType) {
253 case ContextTypeOptionallyBlockable:
254 allowed = client->allowDisplayingInsecureContent(settings && settings->allowDisplayOfInsecureContent(), securityOrigin, url);
255 if (allowed)
256 client->didDisplayInsecureContent();
257 break;
258
259 case ContextTypeBlockable:
260 allowed = client->allowRunningInsecureContent(settings && settings->allowRunningOfInsecureContent(), securityOrigin, url);
261 if (allowed)
262 client->didRunInsecureContent(securityOrigin, url);
263 break;
264
265 case ContextTypeShouldBeBlockable:
266 return false;
267
268 case ContextTypeBlockableUnlessLax:
269 // We map this to either OptionallyBlockable or Blockable above.
270 ASSERT_NOT_REACHED();
271 return true;
272 };
273
274 logToConsole(frame, url, resourceRequest.requestContext(), allowed);
275 return !allowed;
276 }
277
canDisplayInsecureContentInternal(SecurityOrigin * securityOrigin,const KURL & url,const MixedContentType type) const278 bool MixedContentChecker::canDisplayInsecureContentInternal(SecurityOrigin* securityOrigin, const KURL& url, const MixedContentType type) const
279 {
280 // Check the top frame if it differs from MixedContentChecker's m_frame.
281 if (!m_frame->tree().top()->isLocalFrame()) {
282 // FIXME: We need a way to access the top-level frame's MixedContentChecker when that frame
283 // is in a different process from the current frame. Until that is done, we always allow
284 // loads in remote frames.
285 return false;
286 }
287 Frame* top = m_frame->tree().top();
288 if (top != m_frame && !toLocalFrame(top)->loader().mixedContentChecker()->canDisplayInsecureContent(toLocalFrame(top)->document()->securityOrigin(), url))
289 return false;
290
291 // Just count these for the moment, don't block them.
292 if (Platform::current()->isReservedIPAddress(url) && !Platform::current()->isReservedIPAddress(KURL(ParsedURLString, securityOrigin->toString())))
293 UseCounter::count(m_frame->document(), UseCounter::MixedContentPrivateIPInPublicWebsitePassive);
294
295 // Then check the current frame:
296 if (!isMixedContent(securityOrigin, url))
297 return true;
298
299 Settings* settings = m_frame->settings();
300 bool allowed = client()->allowDisplayingInsecureContent(settings && settings->allowDisplayOfInsecureContent(), securityOrigin, url);
301 logWarning(allowed, url, type);
302
303 if (allowed)
304 client()->didDisplayInsecureContent();
305
306 return allowed;
307 }
308
canRunInsecureContentInternal(SecurityOrigin * securityOrigin,const KURL & url,const MixedContentType type) const309 bool MixedContentChecker::canRunInsecureContentInternal(SecurityOrigin* securityOrigin, const KURL& url, const MixedContentType type) const
310 {
311 // Check the top frame if it differs from MixedContentChecker's m_frame.
312 if (!m_frame->tree().top()->isLocalFrame()) {
313 // FIXME: We need a way to access the top-level frame's MixedContentChecker when that frame
314 // is in a different process from the current frame. Until that is done, we always allow
315 // loads in remote frames.
316 return true;
317 }
318 Frame* top = m_frame->tree().top();
319 if (top != m_frame && !toLocalFrame(top)->loader().mixedContentChecker()->canRunInsecureContent(toLocalFrame(top)->document()->securityOrigin(), url))
320 return false;
321
322 // Just count these for the moment, don't block them.
323 if (Platform::current()->isReservedIPAddress(url) && !Platform::current()->isReservedIPAddress(KURL(ParsedURLString, securityOrigin->toString())))
324 UseCounter::count(m_frame->document(), UseCounter::MixedContentPrivateIPInPublicWebsiteActive);
325
326 // Then check the current frame:
327 if (!isMixedContent(securityOrigin, url))
328 return true;
329
330 Settings* settings = m_frame->settings();
331 bool allowedPerSettings = settings && (settings->allowRunningOfInsecureContent() || ((type == WebSocket) && settings->allowConnectingInsecureWebSocket()));
332 bool allowed = client()->allowRunningInsecureContent(allowedPerSettings, securityOrigin, url);
333 logWarning(allowed, url, type);
334
335 if (allowed)
336 client()->didRunInsecureContent(securityOrigin, url);
337
338 return allowed;
339 }
340
canFrameInsecureContent(SecurityOrigin * securityOrigin,const KURL & url) const341 bool MixedContentChecker::canFrameInsecureContent(SecurityOrigin* securityOrigin, const KURL& url) const
342 {
343 // If we're dealing with a CORS-enabled scheme, then block mixed frames as active content. Otherwise,
344 // treat frames as passive content.
345 //
346 // FIXME: Remove this temporary hack once we have a reasonable API for launching external applications
347 // via URLs. http://crbug.com/318788 and https://crbug.com/393481
348 if (SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(url.protocol()))
349 return canRunInsecureContentInternal(securityOrigin, url, MixedContentChecker::Execution);
350 return canDisplayInsecureContentInternal(securityOrigin, url, MixedContentChecker::Display);
351 }
352
canConnectInsecureWebSocket(SecurityOrigin * securityOrigin,const KURL & url) const353 bool MixedContentChecker::canConnectInsecureWebSocket(SecurityOrigin* securityOrigin, const KURL& url) const
354 {
355 if (RuntimeEnabledFeatures::laxMixedContentCheckingEnabled())
356 return canDisplayInsecureContentInternal(securityOrigin, url, MixedContentChecker::WebSocket);
357 return canRunInsecureContentInternal(securityOrigin, url, MixedContentChecker::WebSocket);
358 }
359
canSubmitToInsecureForm(SecurityOrigin * securityOrigin,const KURL & url) const360 bool MixedContentChecker::canSubmitToInsecureForm(SecurityOrigin* securityOrigin, const KURL& url) const
361 {
362 // For whatever reason, some folks handle forms via JavaScript, and submit to `javascript:void(0)`
363 // rather than calling `preventDefault()`. We special-case `javascript:` URLs here, as they don't
364 // introduce MixedContent for form submissions.
365 if (url.protocolIs("javascript"))
366 return true;
367
368 // If lax mixed content checking is enabled (noooo!), skip this check entirely.
369 if (RuntimeEnabledFeatures::laxMixedContentCheckingEnabled())
370 return true;
371 return canDisplayInsecureContentInternal(securityOrigin, url, MixedContentChecker::Submission);
372 }
373
logWarning(bool allowed,const KURL & target,const MixedContentType type) const374 void MixedContentChecker::logWarning(bool allowed, const KURL& target, const MixedContentType type) const
375 {
376 StringBuilder message;
377 message.append((allowed ? "" : "[blocked] "));
378 message.append("The page at '" + m_frame->document()->url().elidedString() + "' was loaded over HTTPS, but ");
379 switch (type) {
380 case Display:
381 message.append("displayed insecure content from '" + target.elidedString() + "': this content should also be loaded over HTTPS.\n");
382 break;
383 case Execution:
384 case WebSocket:
385 message.append("ran insecure content from '" + target.elidedString() + "': this content should also be loaded over HTTPS.\n");
386 break;
387 case Submission:
388 message.append("is submitting data to an insecure location at '" + target.elidedString() + "': this content should also be submitted over HTTPS.\n");
389 break;
390 }
391 MessageLevel messageLevel = allowed ? WarningMessageLevel : ErrorMessageLevel;
392 m_frame->document()->addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, messageLevel, message.toString()));
393 }
394
checkMixedPrivatePublic(LocalFrame * frame,const AtomicString & resourceIPAddress)395 void MixedContentChecker::checkMixedPrivatePublic(LocalFrame* frame, const AtomicString& resourceIPAddress)
396 {
397 if (!frame || !frame->document() || !frame->document()->loader())
398 return;
399
400 KURL documentIP(ParsedURLString, "http://" + frame->document()->loader()->response().remoteIPAddress());
401 KURL resourceIP(ParsedURLString, "http://" + resourceIPAddress);
402
403 // Just count these for the moment, don't block them.
404 //
405 // FIXME: Once we know how we want to check this, adjust the platform APIs to avoid the KURL construction.
406 if (Platform::current()->isReservedIPAddress(resourceIP) && !Platform::current()->isReservedIPAddress(documentIP))
407 UseCounter::count(frame->document(), UseCounter::MixedContentPrivateHostnameInPublicHostname);
408 }
409
trace(Visitor * visitor)410 void MixedContentChecker::trace(Visitor* visitor)
411 {
412 visitor->trace(m_frame);
413 }
414
415 } // namespace blink
416