fix: do not forward Authorization/Cookie to subdomains on redirect
Closes https://github.com/dart-lang/sdk/pull/63256 GitOrigin-RevId: 1cf4d94690343b4df087e7c583154a36580fff71 Change-Id: I186e9338e36ee72714d385444e6fbf3b217ec4ff Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/498424 Commit-Queue: Slava Egorov <vegorov@google.com> Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
1e46f62954
commit
890f7e8aa9
@@ -3059,11 +3059,8 @@ class _HttpClient implements HttpClient {
|
||||
);
|
||||
}
|
||||
|
||||
static bool _isSubdomain(Uri subdomain, Uri domain) {
|
||||
return (subdomain.isScheme(domain.scheme) &&
|
||||
subdomain.port == domain.port &&
|
||||
(subdomain.host == domain.host ||
|
||||
subdomain.host.endsWith("." + domain.host)));
|
||||
static bool _isSameOrigin(Uri a, Uri b) {
|
||||
return a.isScheme(b.scheme) && a.host == b.host && a.port == b.port;
|
||||
}
|
||||
|
||||
// Only visible for testing.
|
||||
@@ -3072,17 +3069,16 @@ class _HttpClient implements HttpClient {
|
||||
required Uri originalUrl,
|
||||
required Uri redirectUrl,
|
||||
}) {
|
||||
if (_isSubdomain(redirectUrl, originalUrl)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const nonRedirectHeaders = [
|
||||
// It is only safe to copy sensitive headers when redirecting to the
|
||||
// same origin (RFC 6454 section 4).
|
||||
const sensitiveHeaders = [
|
||||
"authorization",
|
||||
"www-authenticate",
|
||||
"cookie",
|
||||
"cookie2",
|
||||
];
|
||||
return !nonRedirectHeaders.contains(headerKey.toLowerCase());
|
||||
return !sensitiveHeaders.contains(headerKey.toLowerCase()) ||
|
||||
_isSameOrigin(redirectUrl, originalUrl);
|
||||
}
|
||||
|
||||
Future<_HttpClientRequest> _openUrlFromRequest(
|
||||
|
||||
@@ -582,15 +582,70 @@ void testShouldCopyHeadersOnRedirect() {
|
||||
);
|
||||
checkShouldCopyHeader("cat", "http://foo.com", "http://foo.com:80/foo", true);
|
||||
|
||||
// Redirect to subdomain.
|
||||
// Redirect to subdomain. Credential-carrying headers MUST NOT be forwarded
|
||||
// to a subdomain (the subdomain can be attacker-controlled on shared apex
|
||||
// hosts, and RFC 6265 host-only cookies must not be sent to subdomains).
|
||||
// Non-sensitive headers continue to follow the redirect.
|
||||
checkShouldCopyHeader(
|
||||
"authorization",
|
||||
"https://foo.com",
|
||||
"https://www.foo.com",
|
||||
true,
|
||||
false,
|
||||
);
|
||||
checkShouldCopyHeader(
|
||||
"cookie",
|
||||
"https://foo.com",
|
||||
"https://www.foo.com",
|
||||
false,
|
||||
);
|
||||
checkShouldCopyHeader(
|
||||
"cookie2",
|
||||
"https://foo.com",
|
||||
"https://www.foo.com",
|
||||
false,
|
||||
);
|
||||
checkShouldCopyHeader(
|
||||
"www-authenticate",
|
||||
"https://foo.com",
|
||||
"https://www.foo.com",
|
||||
false,
|
||||
);
|
||||
checkShouldCopyHeader("cat", "https://foo.com", "https://www.foo.com", true);
|
||||
|
||||
// Redirect to same host: all sensitive headers are still forwarded.
|
||||
checkShouldCopyHeader(
|
||||
"cookie",
|
||||
"https://foo.com",
|
||||
"https://foo.com/path",
|
||||
true,
|
||||
);
|
||||
checkShouldCopyHeader(
|
||||
"cookie2",
|
||||
"https://foo.com",
|
||||
"https://foo.com/path",
|
||||
true,
|
||||
);
|
||||
checkShouldCopyHeader(
|
||||
"www-authenticate",
|
||||
"https://foo.com",
|
||||
"https://foo.com/path",
|
||||
true,
|
||||
);
|
||||
|
||||
// Redirect to a deeper subdomain (e.g. attacker-uploads.example.com).
|
||||
checkShouldCopyHeader(
|
||||
"authorization",
|
||||
"https://example.com",
|
||||
"https://attacker-uploads.example.com",
|
||||
false,
|
||||
);
|
||||
checkShouldCopyHeader(
|
||||
"cookie",
|
||||
"https://example.com",
|
||||
"https://attacker-uploads.example.com",
|
||||
false,
|
||||
);
|
||||
|
||||
// Redirect to different domain.
|
||||
checkShouldCopyHeader(
|
||||
"authorization",
|
||||
|
||||
Reference in New Issue
Block a user