Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(226)

Side by Side Diff: content/browser/frame_host/ancestor_throttle.cc

Issue 2488743003: (Re-)introduce AncestorThrottle to handle 'X-Frame-Options'. (Closed)
Patch Set: Addressed comments (@clamy #3) Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/frame_host/ancestor_throttle.h"
6
7 #include "base/metrics/histogram_macros.h"
8 #include "base/strings/string_split.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/stringprintf.h"
11 #include "content/browser/frame_host/frame_tree.h"
12 #include "content/browser/frame_host/frame_tree_node.h"
13 #include "content/browser/frame_host/navigation_handle_impl.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "content/public/browser/navigation_handle.h"
16 #include "content/public/browser/navigation_throttle.h"
17 #include "content/public/common/console_message_level.h"
18 #include "net/http/http_response_headers.h"
19 #include "url/origin.h"
20
21 namespace content {
22
23 namespace {
24 const char kXFrameOptionsSameOriginHistogram[] = "Security.XFrameOptions";
25
26 // This enum is used for UMA metrics. Keep these enums up to date with
27 // tools/metrics/histograms/histograms.xml.
28 enum XFrameOptionsHistogram {
29 // A frame is loaded without any X-Frame-Options header.
30 NONE = 0,
31
32 // X-Frame-Options: DENY.
33 DENY = 1,
34
35 // X-Frame-Options: SAMEORIGIN. The navigation proceeds and every ancestors
alexmos 2016/12/20 01:34:39 nit: s/ancestors have/ancestor has/
arthursonzogni 2016/12/20 10:29:43 Done.
36 // have the same origin.
37 SAMEORIGIN = 2,
38
39 // X-Frame-Options: SAMEORIGIN. The navigation is blocked because the
40 // top-frame doesn't have the same origin.
41 SAMEORIGIN_BLOCKED = 3,
42
43 // X-Frame-Options: SAMEORIGIN. The navigation proceeds despite the fact that
44 // there is an ancestor that doesn't have the same origin.
45 SAMEORIGIN_WITH_BAD_ANCESTOR_CHAIN = 4,
46
47 // X-Frame-Options: ALLOWALL.
48 ALLOWALL = 5,
49
50 // Invalid 'X-Frame-Options' directive encountered.
51 INVALID = 6,
52
53 // The frame sets multiple 'X-Frame-Options' header with conflicting values.
54 CONFLICT = 7,
55
56 // The 'frame-ancestors' CSP directive should take effect instead.
57 BYPASS = 8,
58
59 XFRAMEOPTIONS_HISTOGRAM_MAX = BYPASS
60 };
61
62 } // namespace
63
64 // static
65 std::unique_ptr<NavigationThrottle> AncestorThrottle::MaybeCreateThrottleFor(
66 NavigationHandle* handle) {
67 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
68
69 if (handle->IsInMainFrame())
70 return nullptr;
71
72 return std::unique_ptr<NavigationThrottle>(new AncestorThrottle(handle));
73 }
74
75 AncestorThrottle::~AncestorThrottle() {}
76
77 NavigationThrottle::ThrottleCheckResult
78 AncestorThrottle::WillProcessResponse() {
79 DCHECK(!navigation_handle()->IsInMainFrame());
80
81 NavigationHandleImpl* handle =
82 static_cast<NavigationHandleImpl*>(navigation_handle());
83
84 std::string header_value;
85 HeaderDisposition disposition =
86 ParseHeader(handle->GetResponseHeaders(), &header_value);
87
88 switch (disposition) {
89 case HeaderDisposition::CONFLICT:
90 ParseError(header_value, disposition);
91 UMA_HISTOGRAM_ENUMERATION(kXFrameOptionsSameOriginHistogram, CONFLICT,
alexmos 2016/12/20 01:34:39 In the past, I've been asked to create a helper fu
arthursonzogni 2016/12/20 10:29:43 Done. Much better like this.
92 XFRAMEOPTIONS_HISTOGRAM_MAX);
93 return NavigationThrottle::BLOCK_RESPONSE;
94
95 case HeaderDisposition::INVALID:
96 ParseError(header_value, disposition);
97 UMA_HISTOGRAM_ENUMERATION(kXFrameOptionsSameOriginHistogram, INVALID,
98 XFRAMEOPTIONS_HISTOGRAM_MAX);
99 // TODO(mkwst): Consider failing here.
100 return NavigationThrottle::PROCEED;
101
102 case HeaderDisposition::DENY:
103 ConsoleError(disposition);
104 UMA_HISTOGRAM_ENUMERATION(kXFrameOptionsSameOriginHistogram, DENY,
105 XFRAMEOPTIONS_HISTOGRAM_MAX);
106 return NavigationThrottle::BLOCK_RESPONSE;
107
108 case HeaderDisposition::SAMEORIGIN: {
109 url::Origin current_origin(navigation_handle()->GetURL());
110 url::Origin top_origin =
111 handle->frame_tree_node()->frame_tree()->root()->current_origin();
112
113 // Block the request when the top-frame has not the same origin.
114 if (!top_origin.IsSameOriginWith(current_origin)) {
115 UMA_HISTOGRAM_ENUMERATION(kXFrameOptionsSameOriginHistogram,
116 SAMEORIGIN_BLOCKED,
117 XFRAMEOPTIONS_HISTOGRAM_MAX);
118 ConsoleError(disposition);
119 UMA_HISTOGRAM_ENUMERATION(kXFrameOptionsSameOriginHistogram,
120 SAMEORIGIN_BLOCKED,
121 XFRAMEOPTIONS_HISTOGRAM_MAX);
122 return NavigationThrottle::BLOCK_RESPONSE;
123 }
124
125 // Do not block the request when one of the ancestor has not the same
126 // origin, but count how many times it happens for statistics.
127 FrameTreeNode* parent = handle->frame_tree_node()->parent();
128 while (parent) {
129 if (!parent->current_origin().IsSameOriginWith(current_origin)) {
130 UMA_HISTOGRAM_ENUMERATION(kXFrameOptionsSameOriginHistogram,
131 SAMEORIGIN_WITH_BAD_ANCESTOR_CHAIN,
132 XFRAMEOPTIONS_HISTOGRAM_MAX);
133 return NavigationThrottle::PROCEED;
134 }
135 parent = parent->parent();
136 }
137 UMA_HISTOGRAM_ENUMERATION(kXFrameOptionsSameOriginHistogram, SAMEORIGIN,
138 XFRAMEOPTIONS_HISTOGRAM_MAX);
139 return NavigationThrottle::PROCEED;
140 }
141
142 case HeaderDisposition::NONE:
143 UMA_HISTOGRAM_ENUMERATION(kXFrameOptionsSameOriginHistogram,
144 NONE,
145 XFRAMEOPTIONS_HISTOGRAM_MAX);
146 return NavigationThrottle::PROCEED;
147 case HeaderDisposition::BYPASS:
148 UMA_HISTOGRAM_ENUMERATION(kXFrameOptionsSameOriginHistogram,
149 BYPASS,
150 XFRAMEOPTIONS_HISTOGRAM_MAX);
151 return NavigationThrottle::PROCEED;
152 case HeaderDisposition::ALLOWALL:
153 UMA_HISTOGRAM_ENUMERATION(kXFrameOptionsSameOriginHistogram,
154 ALLOWALL,
155 XFRAMEOPTIONS_HISTOGRAM_MAX);
156 return NavigationThrottle::PROCEED;
157 }
158 NOTREACHED();
159 return NavigationThrottle::BLOCK_RESPONSE;
160 }
161
162 AncestorThrottle::AncestorThrottle(NavigationHandle* handle)
163 : NavigationThrottle(handle) {}
164
165 void AncestorThrottle::ParseError(const std::string& value,
166 HeaderDisposition disposition) {
167 DCHECK(disposition == HeaderDisposition::CONFLICT ||
168 disposition == HeaderDisposition::INVALID);
169
170 std::string message;
171 if (disposition == HeaderDisposition::CONFLICT) {
172 message = base::StringPrintf(
173 "Refused to display '%s' in a frame because it set multiple "
174 "'X-Frame-Options' headers with conflicting values "
175 "('%s'). Falling back to 'deny'.",
176 navigation_handle()->GetURL().spec().c_str(), value.c_str());
177 } else {
178 message = base::StringPrintf(
179 "Invalid 'X-Frame-Options' header encountered when loading '%s': "
180 "'%s' is not a recognized directive. The header will be ignored.",
181 navigation_handle()->GetURL().spec().c_str(), value.c_str());
182 }
183
184 // Log a console error in the parent of the current RenderFrameHost (as
185 // the current RenderFrameHost itself doesn't yet have a document).
186 navigation_handle()->GetRenderFrameHost()->GetParent()->AddMessageToConsole(
187 CONSOLE_MESSAGE_LEVEL_ERROR, message);
188 }
189
190 void AncestorThrottle::ConsoleError(HeaderDisposition disposition) {
191 DCHECK(disposition == HeaderDisposition::DENY ||
192 disposition == HeaderDisposition::SAMEORIGIN);
193 std::string message = base::StringPrintf(
194 "Refused to display '%s' in a frame because it set 'X-Frame-Options' "
195 "to '%s'.",
196 navigation_handle()->GetURL().spec().c_str(),
197 disposition == HeaderDisposition::DENY ? "deny" : "sameorigin");
198
199 // Log a console error in the parent of the current RenderFrameHost (as
200 // the current RenderFrameHost itself doesn't yet have a document).
201 navigation_handle()->GetRenderFrameHost()->GetParent()->AddMessageToConsole(
202 CONSOLE_MESSAGE_LEVEL_ERROR, message);
203 }
204
205 AncestorThrottle::HeaderDisposition AncestorThrottle::ParseHeader(
206 const net::HttpResponseHeaders* headers,
207 std::string* header_value) {
208 DCHECK(header_value);
209 if (!headers)
210 return HeaderDisposition::NONE;
211
212 // Process the 'X-Frame-Options header as per Section 2 of RFC7034:
213 // https://tools.ietf.org/html/rfc7034#section-2
214 //
215 // Note that we do not support the 'ALLOW-FROM' value, and we special-case
216 // the invalid "ALLOWALL" value due to its prevalance in the wild.
217 HeaderDisposition result = HeaderDisposition::NONE;
218 size_t iter = 0;
219 std::string value;
220 while (headers->EnumerateHeader(&iter, "x-frame-options", &value)) {
221 HeaderDisposition current = HeaderDisposition::INVALID;
222
223 base::StringPiece trimmed =
224 base::TrimWhitespaceASCII(value, base::TRIM_ALL);
225 if (!header_value->empty())
226 header_value->append(", ");
227 header_value->append(trimmed.as_string());
228
229 if (base::LowerCaseEqualsASCII(trimmed, "deny"))
230 current = HeaderDisposition::DENY;
231 else if (base::LowerCaseEqualsASCII(trimmed, "allowall"))
232 current = HeaderDisposition::ALLOWALL;
233 else if (base::LowerCaseEqualsASCII(trimmed, "sameorigin"))
234 current = HeaderDisposition::SAMEORIGIN;
235 else
236 current = HeaderDisposition::INVALID;
237
238 if (result == HeaderDisposition::NONE)
239 result = current;
240 else if (result != current)
241 result = HeaderDisposition::CONFLICT;
242 }
243
244 // If 'X-Frame-Options' would potentially block the response, check whether
245 // the 'frame-ancestors' CSP directive should take effect instead. See
246 // https://www.w3.org/TR/CSP/#frame-ancestors-and-frame-options
247 if (result != HeaderDisposition::NONE &&
248 result != HeaderDisposition::ALLOWALL) {
249 iter = 0;
250 value = std::string();
251 while (headers->EnumerateHeader(&iter, "content-security-policy", &value)) {
252 // TODO(mkwst): 'frame-ancestors' is currently handled in Blink. We should
253 // handle it here instead. Until then, don't block the request, and let
254 // Blink handle it. https://crbug.com/555418
255 std::vector<std::string> tokens = base::SplitString(
256 value, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
257 if (std::count_if(tokens.begin(), tokens.end(), [](std::string token) {
alexmos 2016/12/20 01:34:39 nit: this might be a bit more readable if the cond
arthursonzogni 2016/12/20 10:29:43 Yes I agree, but instead, I replaced std::count_if
258 // The trailing " " is intentional; we'd otherwise match
259 // "frame-ancestors-is-not-this-directive".
260 return base::StartsWith(token, "frame-ancestors ",
261 base::CompareCase::INSENSITIVE_ASCII);
262 })) {
263 return HeaderDisposition::BYPASS;
264 }
265 }
266 }
267 return result;
268 }
269
270 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698