Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upAdd domain and path checks for secure cookies eviction #14491
Conversation
highfive
commented
Dec 8, 2016
|
Heads up! This PR modifies the following files:
|
highfive
commented
Dec 8, 2016
|
r? @jdm |
| // Step 1 | ||
| // https://www.ietf.org/id/draft-ietf-httpbis-cookie-alone-01.txt Step 2 | ||
| if !cookie.cookie.secure && source == CookieSource::HTTP && | ||
| cookies.iter().filter(|c| { |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
We will definitely need tests verifying that the attacks described in the spec fail. |
| @@ -62,6 +74,12 @@ impl CookieStorage { | |||
|
|
|||
| // http://tools.ietf.org/html/rfc6265#section-5.3 | |||
| pub fn push(&mut self, mut cookie: Cookie, source: CookieSource) { | |||
| // https://www.ietf.org/id/draft-ietf-httpbis-cookie-alone-01.txt Step 1 | |||
| if cookie.cookie.secure && source == CookieSource::HTTP { | |||
| // New cookie is not from a "secure" protocol, ignore. | |||
This comment has been minimized.
This comment has been minimized.
jdm
Dec 8, 2016
Member
CookieSource::HTTP only means that it's an HTTP/HTTPS request, rather than document.cookie. Let's add a ServoUrl argument to CookieSource::HTTP so we can check it here.
| cookies.iter().any(|c| { | ||
| c.cookie.name == cookie.cookie.name && | ||
| c.cookie.secure && | ||
| domain_match(c.cookie.domain, cookie.cookie.domain) && |
This comment has been minimized.
This comment has been minimized.
| string == domain_string || (string.ends_with(domain_string) && | ||
| string.as_bytes()[string.len()-domain_string.len()-1] == b'.' && | ||
| string.parse::<Ipv4Addr>().is_err() && | ||
| string.parse::<Ipv6Addr>().is_err()) |
This comment has been minimized.
This comment has been minimized.
| @@ -37,23 +37,35 @@ impl CookieStorage { | |||
| let domain = reg_host(cookie.cookie.domain.as_ref().unwrap_or(&"".to_string())); | |||
| let cookies = self.cookies_map.entry(domain).or_insert(vec![]); | |||
|
|
|||
| // Step 1 | |||
| // https://www.ietf.org/id/draft-ietf-httpbis-cookie-alone-01.txt Step 2 | |||
| if !cookie.cookie.secure && source == CookieSource::HTTP && | |||
This comment has been minimized.
This comment has been minimized.
|
|
||
| c.cookie.name == cookie.cookie.name && | ||
| c.cookie.secure && | ||
| c.cookie.domain == cookie.cookie.domain && |
This comment has been minimized.
This comment has been minimized.
jdm
Dec 9, 2016
Member
What happened to performing the symmetric domain matches from the spec? That's different than checking if the domains for the two cookies are identical.
This comment has been minimized.
This comment has been minimized.
KiChjang
Dec 9, 2016
Author
Member
I was thinking that bi-directional domain-matching is equivalent to equality comparison. RFC 2965 states the following:
Host A's name domain-matches host B's if * their host name strings string-compare equal; or * A is a HDN string and has the form NB, where N is a non-empty name string, B has the form .B', and B' is a HDN string. (So, x.y.com domain-matches .Y.com but not Y.com.)
This comment has been minimized.
This comment has been minimized.
jdm
Dec 9, 2016
Member
It's the second point that doesn't get covered by the equality check here, since if the domains are not strictly equal they can still match.
This comment has been minimized.
This comment has been minimized.
KiChjang
Dec 10, 2016
•
Author
Member
Yes, but since it's checking bi-directionally, I don't think it's possible where A domain-matches B and B domain-matches A but A is not equal to B.
This comment has been minimized.
This comment has been minimized.
KiChjang
Dec 10, 2016
•
Author
Member
Basically it boils down to the statement "if two strings are suffixes of each other, then they are equal".
This comment has been minimized.
This comment has been minimized.
KiChjang
Dec 10, 2016
Author
Member
Nevermind, the IETF draft did not say "and vice versa", it said "or vice versa".
| Cookie::path_match(new, existing) | ||
| } else { | ||
| cookie.cookie.path == c.cookie.path | ||
| }; |
This comment has been minimized.
This comment has been minimized.
jdm
Dec 9, 2016
Member
Our cookies always have a path according to https://github.com/KiChjang/servo/blob/cf08600e5f390b72a6e094e74633326bad204908/components/net/cookie.rs#L69-L74, so let's use expect on both of them instead.
| false | ||
| string == domain_string || | ||
| (string.ends_with(domain_string) && | ||
| string.as_bytes()[string.len()-domain_string.len()-1] == b'.' && |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Note to self: be sure that these changes do not prevent a page using document.cookie to set a secure cookie if the page running the JS has a secure origin. |
|
|
|
Good start on the tests. We need more thorough coverage for some of the cases to be sure we're not breaking the existing implementation and to be sure we're properly blocking all the cases that the specification calls out. |
| @@ -37,6 +37,21 @@ impl CookieStorage { | |||
| let domain = reg_host(cookie.cookie.domain.as_ref().unwrap_or(&"".to_string())); | |||
| let cookies = self.cookies_map.entry(domain).or_insert(vec![]); | |||
|
|
|||
| // https://www.ietf.org/id/draft-ietf-httpbis-cookie-alone-01.txt Step 2 | |||
| if !cookie.cookie.secure && !source.is_secure_protocol() && | |||
| cookies.iter().any(|c| | |||
This comment has been minimized.
This comment has been minimized.
jdm
Dec 15, 2016
Member
Let's do:
if !cookie.cookie.secure && !source.is_secure_protocol() {
let new_domain = cookie.cookie.domain.as_ref().unwrap();
let new_path = cookie.cookie.path.as_ref().unwrap();
let any_overlapping = cookies.iter().any(|c| {
let existing_domain = c.cookie.domain.as_ref().unwrap();
let existing_path = c.cookie.path.as_ref().unwrap();
c.cookie.name == cookie.cookie.name &&
c.cookie.secure &&
(Cookie::domain_match(new_domain, existing_domain) ||
Cookie::domain_match(existing_domain, new_domain)) &&
Cookie::path_match(new_path, existing_path)
});
if any_overlapping {
return Err(());
}
}| pub enum CookieSource { | ||
| /// An HTTP API | ||
| HTTP, | ||
| HTTP(ServoUrl), | ||
| /// A non-HTTP API | ||
| NonHTTP, |
This comment has been minimized.
This comment has been minimized.
jdm
Dec 15, 2016
Member
I think we're going to have to add a ServoUrl to this case to, or we'll break allowing pages to set secure cookies via document.cookie when loaded via a secure scheme.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| #[test] | ||
| fn test_insecure_cookies_cannot_evict_secure_cookie() { | ||
| let mut storage = CookieStorage::new(5); | ||
| let url = ServoUrl::parse("https://home.example.org:8888/cookie-parser?0001").unwrap(); |
This comment has been minimized.
This comment has been minimized.
| storage.push(cookie, source.clone()); | ||
| } | ||
|
|
||
| let url = ServoUrl::parse("http://home.example.org:8888/cookie-parser?0001").unwrap(); |
This comment has been minimized.
This comment has been minimized.
| add_cookie_to_storage(&mut storage, &url, "foo=value; Domain=home.example.org"); | ||
| add_cookie_to_storage(&mut storage, &url, "foo2=value; Domain=.example.org"); | ||
| add_cookie_to_storage(&mut storage, &url, "foo3=value; Path=/foo/bar"); | ||
| add_cookie_to_storage(&mut storage, &url, "foo4=value; Path=/foo"); |
This comment has been minimized.
This comment has been minimized.
jdm
Dec 15, 2016
Member
We need another test that tries setting these cookies using a secure scheme, too.
| let source = CookieSource::HTTP(url.clone()); | ||
| assert_eq!(storage.cookies_for_url(&url, source).unwrap(), "foo4=bar; foo3=bar; foo4=value; foo=bar; foo2=bar"); | ||
| } | ||
|
|
This comment has been minimized.
This comment has been minimized.
jdm
Dec 15, 2016
Member
We should add a test that it's still possible to set secure cookies using a NonHTTP source and a secure URL.
|
Seems like the rebase went a bit wonky here? |
|
Still only rebased; haven't gotten around to address comments yet. |
|
I finally sat down and reasoned my way through the test results. This looks good to me; thanks for working on it! |
|
|
Add domain and path checks for secure cookies eviction Fixes #14477. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/14491) <!-- Reviewable:end -->
|
|
KiChjang commentedDec 8, 2016
•
edited by larsbergstrom
Fixes #14477.
This change is