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

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

Issue 2488743003: (Re-)introduce AncestorThrottle to handle 'X-Frame-Options'. (Closed)
Patch Set: Add histogram. 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 const char kXFrameOptionsSameOriginHistogram[] =
24 "Security.XFrameOptions.SameOrigin";
25
26 // static
27 std::unique_ptr<NavigationThrottle> AncestorThrottle::MaybeCreateThrottleFor(
28 NavigationHandle* handle) {
29 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
30
31 if (handle->IsInMainFrame())
32 return nullptr;
33
34 return std::unique_ptr<NavigationThrottle>(new AncestorThrottle(handle));
35 }
36
37 AncestorThrottle::~AncestorThrottle() {}
38
39 NavigationThrottle::ThrottleCheckResult
40 AncestorThrottle::WillProcessResponse() {
41 DCHECK(!navigation_handle()->IsInMainFrame());
42
43 NavigationHandleImpl* handle =
44 static_cast<NavigationHandleImpl*>(navigation_handle());
45
46 std::string header_value;
47 HeaderDisposition disposition =
48 ParseHeader(handle->GetResponseHeaders(), &header_value);
49
50 if (disposition != HeaderDisposition::NONE)
51 UMA_HISTOGRAM_ENUMERATION(kXFrameOptionsSameOriginHistogram, TOTAL,
clamy 2016/12/16 15:21:43 I find the value TOTAL a bit weird here. My sugges
arthursonzogni 2016/12/19 12:01:18 Done.
52 XFRAMEOPTIONS_SAMEORIGIN_COUNT);
53 switch (disposition) {
54 case HeaderDisposition::CONFLICT:
55 ParseError(header_value, disposition);
56 return NavigationThrottle::BLOCK_RESPONSE;
57
58 case HeaderDisposition::INVALID:
59 ParseError(header_value, disposition);
60 // TODO(mkwst): Consider failing here.
61 return NavigationThrottle::PROCEED;
62
63 case HeaderDisposition::DENY:
64 ConsoleError(disposition);
65 return NavigationThrottle::BLOCK_RESPONSE;
66
67 case HeaderDisposition::SAMEORIGIN: {
68 UMA_HISTOGRAM_ENUMERATION(kXFrameOptionsSameOriginHistogram, SAME_ORIGIN,
69 XFRAMEOPTIONS_SAMEORIGIN_COUNT);
70 url::Origin current_origin(navigation_handle()->GetURL());
71 url::Origin top_origin =
72 handle->frame_tree_node()->frame_tree()->root()->current_origin();
73
74 // Block the request when the top-frame has not the same origin.
75 if (!top_origin.IsSameOriginWith(current_origin)) {
76 UMA_HISTOGRAM_ENUMERATION(kXFrameOptionsSameOriginHistogram,
77 SAME_ORIGIN_BLOCKED,
78 XFRAMEOPTIONS_SAMEORIGIN_COUNT);
79 ConsoleError(disposition);
80 return NavigationThrottle::BLOCK_RESPONSE;
81 }
82
83 // Do not block the request when one of the ancestor has not the same
84 // origin, but count how many times it happens for statistics.
85 FrameTreeNode* parent = handle->frame_tree_node()->parent();
86 while (parent) {
87 if (!parent->current_origin().IsSameOriginWith(current_origin)) {
88 UMA_HISTOGRAM_ENUMERATION(kXFrameOptionsSameOriginHistogram,
clamy 2016/12/16 15:21:43 I find it very weird that we're registering this v
arthursonzogni 2016/12/19 12:01:18 Done.
89 SAME_ORIGIN_WITH_BAD_ANCESTOR_CHAIN,
90 XFRAMEOPTIONS_SAMEORIGIN_COUNT);
91 break;
92 }
93 parent = parent->parent();
94 }
95
96 return NavigationThrottle::PROCEED;
97 }
98
99 case HeaderDisposition::NONE:
100 case HeaderDisposition::BYPASS:
101 case HeaderDisposition::ALLOWALL:
102 return NavigationThrottle::PROCEED;
103 }
104 NOTREACHED();
105 return NavigationThrottle::BLOCK_RESPONSE;
106 }
107
108 AncestorThrottle::AncestorThrottle(NavigationHandle* handle)
109 : NavigationThrottle(handle) {}
110
111 void AncestorThrottle::ParseError(const std::string& value,
112 HeaderDisposition disposition) {
113 DCHECK(disposition == HeaderDisposition::CONFLICT ||
114 disposition == HeaderDisposition::INVALID);
115
116 std::string message;
117 if (disposition == HeaderDisposition::CONFLICT) {
118 message = base::StringPrintf(
119 "Refused to display '%s' in a frame because it set multiple "
120 "'X-Frame-Options' headers with conflicting values "
121 "('%s'). Falling back to 'deny'.",
122 navigation_handle()->GetURL().spec().c_str(), value.c_str());
123 } else {
124 message = base::StringPrintf(
125 "Invalid 'X-Frame-Options' header encountered when loading '%s': "
126 "'%s' is not a recognized directive. The header will be ignored.",
127 navigation_handle()->GetURL().spec().c_str(), value.c_str());
128 }
129
130 // Log a console error in the parent of the current RenderFrameHost (as
131 // the current RenderFrameHost itself doesn't yet have a document).
132 navigation_handle()->GetRenderFrameHost()->GetParent()->AddMessageToConsole(
133 CONSOLE_MESSAGE_LEVEL_ERROR, message);
134 }
135
136 void AncestorThrottle::ConsoleError(HeaderDisposition disposition) {
137 DCHECK(disposition == HeaderDisposition::DENY ||
138 disposition == HeaderDisposition::SAMEORIGIN);
139 std::string message = base::StringPrintf(
140 "Refused to display '%s' in a frame because it set 'X-Frame-Options' "
141 "to '%s'.",
142 navigation_handle()->GetURL().spec().c_str(),
143 disposition == HeaderDisposition::DENY ? "deny" : "sameorigin");
144
145 // Log a console error in the parent of the current RenderFrameHost (as
146 // the current RenderFrameHost itself doesn't yet have a document).
147 navigation_handle()->GetRenderFrameHost()->GetParent()->AddMessageToConsole(
148 CONSOLE_MESSAGE_LEVEL_ERROR, message);
149 }
150
151 AncestorThrottle::HeaderDisposition AncestorThrottle::ParseHeader(
152 const net::HttpResponseHeaders* headers,
153 std::string* header_value) {
154 DCHECK(header_value);
155 if (!headers)
156 return HeaderDisposition::NONE;
157
158 // Process the 'X-Frame-Options header as per Section 2 of RFC7034:
159 // https://tools.ietf.org/html/rfc7034#section-2
160 //
161 // Note that we do not support the 'ALLOW-FROM' value, and we special-case
162 // the invalid "ALLOWALL" value due to its prevalance in the wild.
163 HeaderDisposition result = HeaderDisposition::NONE;
164 size_t iter = 0;
165 std::string value;
166 while (headers->EnumerateHeader(&iter, "x-frame-options", &value)) {
167 HeaderDisposition current = HeaderDisposition::INVALID;
168
169 base::StringPiece trimmed =
170 base::TrimWhitespaceASCII(value, base::TRIM_ALL);
171 if (!header_value->empty())
172 header_value->append(", ");
173 header_value->append(trimmed.as_string());
174
175 if (base::LowerCaseEqualsASCII(trimmed, "deny"))
176 current = HeaderDisposition::DENY;
177 else if (base::LowerCaseEqualsASCII(trimmed, "allowall"))
178 current = HeaderDisposition::ALLOWALL;
179 else if (base::LowerCaseEqualsASCII(trimmed, "sameorigin"))
180 current = HeaderDisposition::SAMEORIGIN;
181 else
182 current = HeaderDisposition::INVALID;
183
184 if (result == HeaderDisposition::NONE)
185 result = current;
186 else if (result != current)
187 result = HeaderDisposition::CONFLICT;
188 }
189
190 // If 'X-Frame-Options' would potentially block the response, check whether
191 // the 'frame-ancestors' CSP directive should take effect instead. See
192 // https://www.w3.org/TR/CSP/#frame-ancestors-and-frame-options
193 if (result != HeaderDisposition::NONE &&
194 result != HeaderDisposition::ALLOWALL) {
195 iter = 0;
196 value = std::string();
197 while (headers->EnumerateHeader(&iter, "content-security-policy", &value)) {
198 // TODO(mkwst): 'frame-ancestors' is currently handled in Blink. We should
199 // handle it here instead. Until then, don't block the request, and let
200 // Blink handle it. https://crbug.com/555418
201 std::vector<std::string> tokens = base::SplitString(
202 value, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
203 if (std::count_if(tokens.begin(), tokens.end(), [](std::string token) {
204 // The trailing " " is intentional; we'd otherwise match
205 // "frame-ancestors-is-not-this-directive".
206 return base::StartsWith(token, "frame-ancestors ",
207 base::CompareCase::INSENSITIVE_ASCII);
208 })) {
209 return HeaderDisposition::BYPASS;
210 }
211 }
212 }
213 return result;
214 }
215
216 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698