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

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 (@alexmos #2) 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 ancestor
36 // has 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.
clamy 2016/12/20 14:27:01 nit:s/header/headers
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 void RecordXFrameOptionsUsage(XFrameOptionsHistogram usage) {
63 UMA_HISTOGRAM_ENUMERATION(kXFrameOptionsSameOriginHistogram, usage,
64 XFRAMEOPTIONS_HISTOGRAM_MAX);
65 }
66
67 bool HeadersContainFrameAncestorsCSP(const net::HttpResponseHeaders* headers) {
68 size_t iter = 0;
69 std::string value;
70 while (headers->EnumerateHeader(&iter, "content-security-policy", &value)) {
71 // A content-security-policy is a semicolon-separated list of directives.
72 for (const auto& directive : base::SplitStringPiece(
73 value, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
74 // The trailing " " is intentional; we'd otherwise match
75 // "frame-ancestors-is-not-this-directive".
76 if (base::StartsWith(directive, "frame-ancestors ",
77 base::CompareCase::INSENSITIVE_ASCII))
78 return true;
79 }
80 }
81 return false;
82 }
83
84 } // namespace
85
86 // static
87 std::unique_ptr<NavigationThrottle> AncestorThrottle::MaybeCreateThrottleFor(
88 NavigationHandle* handle) {
89 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
90
91 if (handle->IsInMainFrame())
92 return nullptr;
93
94 return std::unique_ptr<NavigationThrottle>(new AncestorThrottle(handle));
95 }
96
97 AncestorThrottle::~AncestorThrottle() {}
98
99 NavigationThrottle::ThrottleCheckResult
100 AncestorThrottle::WillProcessResponse() {
101 DCHECK(!navigation_handle()->IsInMainFrame());
102
103 NavigationHandleImpl* handle =
104 static_cast<NavigationHandleImpl*>(navigation_handle());
105
106 std::string header_value;
107 HeaderDisposition disposition =
108 ParseHeader(handle->GetResponseHeaders(), &header_value);
109
110 switch (disposition) {
111 case HeaderDisposition::CONFLICT:
112 ParseError(header_value, disposition);
113 RecordXFrameOptionsUsage(CONFLICT);
114 return NavigationThrottle::BLOCK_RESPONSE;
115
116 case HeaderDisposition::INVALID:
117 ParseError(header_value, disposition);
118 RecordXFrameOptionsUsage(INVALID);
119 // TODO(mkwst): Consider failing here.
120 return NavigationThrottle::PROCEED;
121
122 case HeaderDisposition::DENY:
123 ConsoleError(disposition);
124 RecordXFrameOptionsUsage(DENY);
125 return NavigationThrottle::BLOCK_RESPONSE;
126
127 case HeaderDisposition::SAMEORIGIN: {
128 url::Origin current_origin(navigation_handle()->GetURL());
129 url::Origin top_origin =
130 handle->frame_tree_node()->frame_tree()->root()->current_origin();
131
132 // Block the request when the top-frame has not the same origin.
133 if (!top_origin.IsSameOriginWith(current_origin)) {
134 RecordXFrameOptionsUsage(SAMEORIGIN_BLOCKED);
135 ConsoleError(disposition);
136 RecordXFrameOptionsUsage(SAMEORIGIN_BLOCKED);
137 return NavigationThrottle::BLOCK_RESPONSE;
138 }
139
140 // Do not block the request when one of the ancestor has not the same
141 // origin, but count how many times it happens for statistics.
142 FrameTreeNode* parent = handle->frame_tree_node()->parent();
143 while (parent) {
144 if (!parent->current_origin().IsSameOriginWith(current_origin)) {
145 RecordXFrameOptionsUsage(SAMEORIGIN_WITH_BAD_ANCESTOR_CHAIN);
146 return NavigationThrottle::PROCEED;
147 }
148 parent = parent->parent();
149 }
150 RecordXFrameOptionsUsage(SAMEORIGIN);
151 return NavigationThrottle::PROCEED;
152 }
153
154 case HeaderDisposition::NONE:
155 RecordXFrameOptionsUsage(NONE);
156 return NavigationThrottle::PROCEED;
157 case HeaderDisposition::BYPASS:
158 RecordXFrameOptionsUsage(BYPASS);
159 return NavigationThrottle::PROCEED;
160 case HeaderDisposition::ALLOWALL:
161 RecordXFrameOptionsUsage(ALLOWALL);
162 return NavigationThrottle::PROCEED;
163 }
164 NOTREACHED();
165 return NavigationThrottle::BLOCK_RESPONSE;
166 }
167
168 AncestorThrottle::AncestorThrottle(NavigationHandle* handle)
169 : NavigationThrottle(handle) {}
170
171 void AncestorThrottle::ParseError(const std::string& value,
172 HeaderDisposition disposition) {
173 DCHECK(disposition == HeaderDisposition::CONFLICT ||
174 disposition == HeaderDisposition::INVALID);
175
176 std::string message;
177 if (disposition == HeaderDisposition::CONFLICT) {
178 message = base::StringPrintf(
179 "Refused to display '%s' in a frame because it set multiple "
180 "'X-Frame-Options' headers with conflicting values "
181 "('%s'). Falling back to 'deny'.",
182 navigation_handle()->GetURL().spec().c_str(), value.c_str());
183 } else {
184 message = base::StringPrintf(
185 "Invalid 'X-Frame-Options' header encountered when loading '%s': "
186 "'%s' is not a recognized directive. The header will be ignored.",
187 navigation_handle()->GetURL().spec().c_str(), value.c_str());
188 }
189
190 // Log a console error in the parent of the current RenderFrameHost (as
191 // the current RenderFrameHost itself doesn't yet have a document).
192 navigation_handle()->GetRenderFrameHost()->GetParent()->AddMessageToConsole(
193 CONSOLE_MESSAGE_LEVEL_ERROR, message);
194 }
195
196 void AncestorThrottle::ConsoleError(HeaderDisposition disposition) {
197 DCHECK(disposition == HeaderDisposition::DENY ||
198 disposition == HeaderDisposition::SAMEORIGIN);
199 std::string message = base::StringPrintf(
200 "Refused to display '%s' in a frame because it set 'X-Frame-Options' "
201 "to '%s'.",
202 navigation_handle()->GetURL().spec().c_str(),
203 disposition == HeaderDisposition::DENY ? "deny" : "sameorigin");
204
205 // Log a console error in the parent of the current RenderFrameHost (as
206 // the current RenderFrameHost itself doesn't yet have a document).
207 navigation_handle()->GetRenderFrameHost()->GetParent()->AddMessageToConsole(
208 CONSOLE_MESSAGE_LEVEL_ERROR, message);
209 }
210
211 AncestorThrottle::HeaderDisposition AncestorThrottle::ParseHeader(
212 const net::HttpResponseHeaders* headers,
213 std::string* header_value) {
214 DCHECK(header_value);
215 if (!headers)
216 return HeaderDisposition::NONE;
217
218 // Process the 'X-Frame-Options header as per Section 2 of RFC7034:
219 // https://tools.ietf.org/html/rfc7034#section-2
220 //
221 // Note that we do not support the 'ALLOW-FROM' value, and we special-case
222 // the invalid "ALLOWALL" value due to its prevalance in the wild.
223 HeaderDisposition result = HeaderDisposition::NONE;
224 size_t iter = 0;
225 std::string value;
226 while (headers->EnumerateHeader(&iter, "x-frame-options", &value)) {
227 HeaderDisposition current = HeaderDisposition::INVALID;
228
229 base::StringPiece trimmed =
230 base::TrimWhitespaceASCII(value, base::TRIM_ALL);
231 if (!header_value->empty())
232 header_value->append(", ");
233 header_value->append(trimmed.as_string());
234
235 if (base::LowerCaseEqualsASCII(trimmed, "deny"))
236 current = HeaderDisposition::DENY;
237 else if (base::LowerCaseEqualsASCII(trimmed, "allowall"))
238 current = HeaderDisposition::ALLOWALL;
239 else if (base::LowerCaseEqualsASCII(trimmed, "sameorigin"))
240 current = HeaderDisposition::SAMEORIGIN;
241 else
242 current = HeaderDisposition::INVALID;
243
244 if (result == HeaderDisposition::NONE)
245 result = current;
246 else if (result != current)
247 result = HeaderDisposition::CONFLICT;
248 }
249
250 // If 'X-Frame-Options' would potentially block the response, check whether
251 // the 'frame-ancestors' CSP directive should take effect instead. See
252 // https://www.w3.org/TR/CSP/#frame-ancestors-and-frame-options
253 if (result != HeaderDisposition::NONE &&
254 result != HeaderDisposition::ALLOWALL &&
255 HeadersContainFrameAncestorsCSP(headers)) {
256 // TODO(mkwst): 'frame-ancestors' is currently handled in Blink. We should
257 // handle it here instead. Until then, don't block the request, and let
258 // Blink handle it. https://crbug.com/555418
259 return HeaderDisposition::BYPASS;
260 }
261 return result;
262 }
263
264 } // namespace content
OLDNEW
« no previous file with comments | « content/browser/frame_host/ancestor_throttle.h ('k') | content/browser/frame_host/ancestor_throttle_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698