New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.

Issue 821811 link

Starred by 1 user

Issue metadata

Status: Untriaged
Owner: ----
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: ----
Pri: 2
Type: Bug



Sign in to add a comment

Security: HSTS host matching ignores superdomains with includeSubDomains when a subdomain without includeSubDomains is present

Project Member Reported by tnagel@chromium.org, Mar 14 2018

Issue description

VERSION
Chrome Version: 64.0.3282.140 stable (also ToT)
Operating System: gLinux (but likely affects several OSes)

REPRODUCTION CASE
1. Go to chrome://net-internals/#hsts
2. Add HSTS/PKP domain: "sub.example.com" and include subdomains for STS
3. Add HSTS/PKP domain: "sub.sub.example.com" without including subdomains for STS
4. Query HSTS/PKP domain: "x.sub.example.com"
    --> a record is found, working as intended
5. Query HSTS/PKP domain: "x.sub.sub.example.com" 
    --> no record is found, this is the bug

The same behavior is visible in navigations, i.e. http://x.sub.example.com/ redirects to https, whereas http://x.sub.sub.example.com/ doesn't.

RFC6797 mandates a different behavior:

"If, when performing domain name matching any superdomain match
with an asserted includeSubDomains directive is found, or, if no
superdomain matches with asserted includeSubDomains directives
are found and a congruent match is found (with or without an
asserted includeSubDomains directive), ..."
 

Comment 1 by mkwst@chromium.org, Mar 14 2018

Cc: davidben@chromium.org rsleevi@chromium.org mmenke@chromium.org
Labels: Security_Severity-Low Security_Impact-Stable
Status: Untriaged (was: Unconfirmed)
CCing some network folks who might know who feels responsible for HSTS these days.
I didn't think any browser implemented that behaviour. Indeed, we know it's important for a number of deployments to *not* match that behaviour, and to have the implementation behave as today, as that allows 'carveouts'

That is, consider
example.com, includeSubdomains
corp.example.com, no includeSubDomains

The behaviour allows one to 'punch out' corp.google.com from the subdomain list, while otherwise setting it on the superdomain. Without that ability, we know a number of organizations would not deploy HSTS, due to these split horizons.

I'm not sure we should tag as a security bug, since this (is/was) intentional deviation.
Cc: elawrence@chromium.org nhar...@chromium.org

Comment 4 by tnagel@chromium.org, Mar 14 2018

Thanks Ryan! I understand the use case for the current implementation, but that may harm deployment that rely on setting recursive on their primary domain as a security measure. Is this deviation from the spec documented somewhere? Or maybe the spec should be updated?
Our behavior is also pretty weird in that, while includeSubdomains behaves like this between two dynamic pin entries, a non-recursive corp.example.com HSTS header will not mask off a recursive static HSTS entry.

I vaguely recall us considering this a bug when the organization which was relying on this came to us (and specifically asked that static entries have the same behavior, which we declined to do). We talked about adding the carve-out mechanism to admin policy instead so that we could ultimately align this with the spec. Or perhaps it was only me who thought we should go that route, I forget. :-)

This kind of carve-out has security issues because of how cookies work (though we have the __Secure- prefix now), so I think admin policies are a better mechanism.
> This kind of carve-out has security issues because of how cookies work

I should probably have expanded on that:

Servers cannot distinguish cookies set over HTTP and HTTPS, so you need includeSubDomains to plug that hole. Thus having a carve-out for corp.example.com at all is a bad idea, whether it be done via HTTP header or admin policy.

It would be preferable if one did not need this at all. Alas, that's not how a lot of things are deployed, so admin policy would allow us to at least contain the effects to folks who would actually be visiting foo.corp.example.com. It also works better as it doesn't require one hit corp.example.com before visiting foo.corp.example.com. (If we felt extra-inclined, we could even tweak how cookies work based on that policy, but that may be a bit much.)

Comment 7 by tnagel@chromium.org, Mar 15 2018

Cc: msramek@chromium.org
> Our behavior is also pretty weird in that, while includeSubdomains behaves
> like this between two dynamic pin entries, a non-recursive corp.example.com
> HSTS header will not mask off a recursive static HSTS entry.

Really? To me it looks the other way around: In my reading of TransportSecurityState::ShouldUpgradeToSSL() any dynamic entry takes precedence over any static entry. Therefore a dynamic non-recursive entry can punch a hole into a static recursive entry, but a static non-recursive entry cannot affect any dynamic entry. I have a hard time convincing myself that this is working as intended and I agree with David that carveouts via enterprise policy probably would be a better approach.

Comment 8 Deleted

> Therefore a dynamic non-recursive entry can punch a hole into a static recursive entry, but a static non-recursive entry cannot affect any dynamic entry.

Yes, that was the desired behaviour I was speaking about. Staticly loading example.com, dynamically punching out corp.example.com
Project Member

Comment 10 by sheriffbot@chromium.org, Mar 15 2018

Labels: Pri-2
I don't believe that is actually the behavior. In TransportSecurityState::ShouldUpgradeToSSL, if there is a non-recursive dynamic entry, GetDynamicSTSState will not return any state (because nothing matches), which means it will fall through to checking static entries, where it will find a match and force an upgrade.

(It's confusing because the code makes it look like STSState entries where ShouldUpgradeToSSL returns false exist. They don't in the dynamic map and only exist in the static map as a quirk of how the static map merges STS and PKP state together.)
(This is precisely the behavior we were asked to change by the organization in comment #5 and declined to, in favor of admin policy that never materialized.)
To summarize, there's two ways in which the implementation deviates from the RFC:

1. include subdomains does not take precedence:
  * example.com, includeSubdomains
  * corp.example.com, no includeSubDomains
  --> corp.example.com without includeSubDomains takes precedence

2. dynamic takes precedence over static:
  * sub.example.com, no includeSubdomains, static
  * example.com, includeSubdomains, dynamic
  --> example.com with includeSubdomains takes precedence

For someone planning to roll out a complex HSTS deployment, it seems the only way of knowing how Chrome is going to behave is reading our source code. I don't think that's a good state to be in.
Does (2) deviate from the RFC? I thought (2) was the RFC's behavior while (1) was a deviation. (Of course, (2) is interesting in so far as it is inconsistent with (1). Just wanted to make sure we're on the same page.)

One could imaging aligning (2) with (1), but I gather you aren't a fan of that and neither am I. Given the use case Ryan mentioned, probably any option that aligns (1) with (2) will require first adding an admin policy source of punch-outs.

Actually, how do other browsers behave here? Just about everyone implements HSTS at this point.
In my reading of the RFC, I couldn't find any mention of static entries being treated differently than dynamic entries other than that they don't expire. Chrome's behavior of first searching dynamic and then static entries deviates from that. My example was incorrect, though; let me try again:

2. dynamic takes precedence over static:
  * example.com, includeSubdomains, static
  * example.com, no includeSubdomains, dynamic
  --> example.com without includeSubdomains takes precedence

Regarding ways forward, I don't have enough context to have an opinion other than that Chrome and RFC should be brought into alignment (either by changing Chrome or by changing the RFC).
Changing the RFC is not really an option, but nor should we treat the RFC behaviour as the desired correct behaviour.

I think this is Type-Cleanup, doesn't need to RVG, SecSeverity-None, Pri-3
> nor should we treat the RFC behaviour as the desired correct behaviour.

I thought Chrome was aiming to be standards compliant?
I think this is a conversation best not held on a bug, and ideally, we will derestrict this bug. Chrome's interest is in being the user's agent, and at times, that may require deviation from specifications when specifications work against those interests. For example, Chrome allows IP addresses to be treated on the HSTS list (when statically loaded), despite the specification clearly not allowing HSTS to be set for IP addresses. This does, however, help protect certain DNS resolvers.

We take a pragmatic approach to standards, and if you can look at Chrome's involvement in SDOs, has preferred to work with living standards that align with reality rather than idealized standards that, intentional or not, have made poor choices.
I'm fine with derestricting this bug but I still consider it a security issue because site operators may look to the RFC when configuring HSTS and Chrome's deviation from the RFC may cause HSTS to be off in cases where the site operator intended it to be on.
I'll let the security-sheriff derestrict, but I also disagree that it's a security bug, in that it only applies to the case of static HSTS as well, which is not a strict security boundary (best effort).
> I couldn't find any mention of static entries being treated differently than dynamic entries other than that they don't expire.

The spec doesn't says anything about static lists at all. Just "Other mechanisms, such as a client-side pre-loaded Known HSTS Host list, MAY also be used" without details, and some non-normative text saying implementors "should consider including" a pre-load list or some sort. The details are left to the implementor.

Indeed if you were to try to shoehorn the static list into the dynamic list's model, as a dynamic entry with infinite max-age, you would get undesirable behavior where an expiring dynamic entry would not cause the static entry to resurface. Newer HSTS headers with shorter max-age or no includeSubDomains still clobber older HSTS entries with longer ones or includeSubDomains. This is a necessarily practicality for sites that mess up.

But this is all moot because the updated example is still off:

> 2. dynamic takes precedence over static:
>   * example.com, includeSubdomains, static
>   * example.com, no includeSubdomains, dynamic
>   --> example.com without includeSubdomains takes precedence

It doesn't matter that, when you query example.com, whether the static or dynamic includeSubDomains bit is surfaced because you are querying example.com. Were you to query foo.example.com instead, any entries disqualified by includeSubDomains would be filtered out at the matching stage.
Labels: -Type-Bug-Security -Restrict-View-SecurityTeam Type-Bug
De-restricted.
Cc: -mmenke@chromium.org

Sign in to add a comment