Re: [websec] HSTS: max-age=0 interacting with includeSubdomains

Adam Barth <> Tue, 21 August 2012 00:20 UTC

Return-Path: <>
Received: from localhost (localhost []) by (Postfix) with ESMTP id EA24711E809A for <>; Mon, 20 Aug 2012 17:20:04 -0700 (PDT)
X-Virus-Scanned: amavisd-new at
X-Spam-Flag: NO
X-Spam-Score: -2.677
X-Spam-Status: No, score=-2.677 tagged_above=-999 required=5 tests=[AWL=-0.300, BAYES_00=-2.599, FM_FORGED_GMAIL=0.622, J_CHICKENPOX_33=0.6, RCVD_IN_DNSWL_LOW=-1]
Received: from ([]) by localhost ( []) (amavisd-new, port 10024) with ESMTP id FhKzg+EKx-cc for <>; Mon, 20 Aug 2012 17:20:04 -0700 (PDT)
Received: from ( []) by (Postfix) with ESMTP id 4847A11E8097 for <>; Mon, 20 Aug 2012 17:20:04 -0700 (PDT)
Received: by ghbg16 with SMTP id g16so6087547ghb.31 for <>; Mon, 20 Aug 2012 17:20:03 -0700 (PDT)
Received: by with SMTP id g69mr23479494yhn.113.1345508403896; Mon, 20 Aug 2012 17:20:03 -0700 (PDT)
Received: from ( []) by with ESMTPS id o25sm31856158yhm.14.2012. (version=SSLv3 cipher=OTHER); Mon, 20 Aug 2012 17:20:02 -0700 (PDT)
Received: by vbbez10 with SMTP id ez10so6830127vbb.31 for <>; Mon, 20 Aug 2012 17:20:01 -0700 (PDT)
Received: by with SMTP id dp1mr1680048vdb.67.1345508401352; Mon, 20 Aug 2012 17:20:01 -0700 (PDT)
MIME-Version: 1.0
Received: by with HTTP; Mon, 20 Aug 2012 17:19:31 -0700 (PDT)
In-Reply-To: <>
References: <> <>
From: Adam Barth <>
Date: Mon, 20 Aug 2012 17:19:31 -0700
Message-ID: <>
To: Brian Smith <>
Content-Type: text/plain; charset="ISO-8859-1"
Subject: Re: [websec] HSTS: max-age=0 interacting with includeSubdomains
X-Mailman-Version: 2.1.12
Precedence: list
List-Id: Web Application Security Minus Authentication and Transport <>
List-Unsubscribe: <>, <>
List-Archive: <>
List-Post: <>
List-Help: <>
List-Subscribe: <>, <>
X-List-Received-Date: Tue, 21 Aug 2012 00:20:05 -0000

On Mon, Aug 20, 2012 at 5:09 PM, Brian Smith <> wrote:
> Adam Barth wrote:
>> The way the implementation in Chrome works is that max-age=0 only
>> clears the entry for that particular host name.  If there's another
>> shorter host name with includeSubdomains, that isn't affected.
> Let me try to clarify with an example, which I think should be explicitly documented in the spec:
> 1. We visit and receive HSTS with max-age=1234567890 ; includeSubdomains
> 2. We visit and receive HSTS with max-age=0
> Now, is HSTS (because it inherits the HSTS setting from, or is it non-HSTS (because we received a max-age=0 for that host)?

It is HSTS.  Visit (2) would have removed any
entries in the HSTS state table, but there were none, so it didn't
have any effect on the world.

> When we receive a HSTS header from with max-age > 0, then we will treat the expiration time of to be MAX(expiration(, expiration(

A simpler way to think about it is that you keep state for each host.
To answer the question of whether a given host is HSTS, you walk the
list of subdomains and check their unexpired state.

> But, if we receive a HSTS header from with max-age == 0, there are two possibilities:
> 1. We act consistently with the above case and calculate the HSTS expiration time of to be:
>        MAX(expiration(, expiration(
>     == MAX(expiration(, 0)
>     == expiration(
>    This means that we would effectively be ignoring the max=age == 0 value sent by


> 2. We honor the max-age=0 directive sent by and turn off HSTS for, but not for That is, we would treat max-age == 0 from a subdomain as "do not do includeSubdomains inheritance for this subdomain."

This is not correct.  We do "honor" the max-age directive set by by expiring any state.
However, is HSTS due to state specific to,
which is not expired.

> Now, it seems to me that the only reasonable choice is #2, but the spec seems to imply that we should do #1.

Choice #1 seems reasonable to me.

> Here's the problematic scenerio:
> 1. We visit and get the HSTS header with includeSubdomains and an (effectively) infinite expiration time.

There is no such thing as an infinite expiration time.  Let's proceed
assuming you mean "one year" rather than infinite.

> 2. The owners of decides to turn of HSTS for whatever reason (perhaps the domain changed owners, or there's a compatibility issue, or whatever), so they start sending out HSTS with max-age=0 for and for all the subdomains.

That's not a correct way of disabling HSTS after (1).  Instead, they
need only send out an max-age=0 header for itself.

> 3. We visit and get the max-age=0 HSTS header.
> If we choose choice #2, we do what the owners of intended, by treating as non-HSTS right away, without ever needing to visit, which we might NEVER do ever again.

I mean, it's just guesswork as to what they intend.  I can write a
similar story in which the intent is the reverse.

> If we choose choice #1, we will effectively be making HSTS forever, and the domain owner has no way to help us undo it.

They can simply initiate a request to (e.g., by
using an HTTP redirect or an HTML image element) and clear the HSTS
state for that host name.