This page looks best with JavaScript enabled

Mitigation schmitigation: Control HttpOnly cookies through XSS

 ·  ☕ 4 min read

Did you know the ‘HttpOnly’ cookie attribute, intended to make it so that JavaScript can not read or write a particular cookie, is more of a handwavy suggestion than a requirement?

It’s true.

In the modern version of Chrome, regardless of the underlying OS, you can overwrite cookies with HttpOnly set.

If you’ve been around long enough in web security or perhaps dug deep into the literature on web attacks, you might know this as ‘cookie jar overflow’, ‘cookie overflow’ (my favorite because it sounds delicious), or ‘cookie forcing’, because it’s really not a new attack.

However, in the broader community it’s still relatively unknown, to the point where OWASP’s own page on HttpOnly still claims that it’s safe against both reads and writes. And most top articles on cookie protections will still mention HttpOnly as write-protection.

But it’s wrong. Oh, oh so wrong.

Hold onto your head tightly, because otherwise it might explode from the complexity of the attack.

Here’s a piece of code that lets you overwrite any HttpOnly cookie in Chrome (on any OS and device):

1
2
3
4
for (let i=0;i<1000;i++) {
    document.cookie = "cookie"+i+"=overflow";
}
document.cookie = "victimcookie=new_value"

Hope you’re still alive and made it through that wall of code.

The above code assumes ‘victimcookie’ is the cookie you want to overwrite. It works because Chrome has a limited capacity for cookies in its cookie jar. Once you go over this amount, it starts deleting old ones… including HttpOnly cookies.

So, why is this a big deal?

There are two scenarios where this is incredibly juicy for attackers.

Session fixation

Lots of applications have session cookies, and lots of applications will re-use whatever value you happen to already have in your session cookie (this is super common), so by an attacker injecting a XSS payload snippet with something like:

1
2
3
4
for (let i=0;i<1000;i++) {
  document.cookie = "cookie"+i+"=overflow";
}
document.cookie = "session_id=totallysecure"

The attacker sets the victim’s session id to totallysecure, which the server happily accepts when the victim logs in.

Then, by the attacker knowing this ‘secret’ value, the attacker can log in as the end-user by simply setting their own browsers' session_id to the same value.

Manipulate application state

Some applications control the state of the application through cookies.

You might imagine a banking application that once you submit who you send the money to, the server instructs your browser, “hold onto the recipient’s bank id number for me” by setting it as a cookie for use in later steps. To be safe, the bank sets it as HttpOnly so attackers can not modify it.

Because it’s writable, if you know how to, the attacker injects an XSS payload into the page and overwrites the recipient bank id with their own to steal some money.

1
2
3
4
for (let i=0;i<1000;i++) {
  document.cookie = "cookie"+i+"=overflow";
}
document.cookie = "recipient_account=13377331"

Oops.


You might have noticed that the above two ‘scenarios’ for using the cookie overflow attack are among two of the most common reasons you’d want to use cookies in the first place (to persist state or keep track of users), and you’d be right.

We’re all doomed.

How do we mitigate against this?

Okay, but really, how do we get around this, assuming we’re aware of the problem?

Off the top of my head, you have three options:

Use something to provide integrity on top of cookies


You could, for instance, use Json Web Tokens (JWT) containing your data of choice signed by your server and validate them before each (!) use.

session_id=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uX2lkIjoiYmFuYW5hIn0.9OAryZO364i3MvR2wA4wt1mfh0GuK4N5rRGySyvQGq8

(The above JWT token contains "session_id": "banana" in its payload)

You could also manually attach an HMAC to whatever values you need ingegrity checked together by some delimiter and verify on the server-side that the HMAC is the same as you would expect.

session_id=123123123banana|1fd3be2e8e75c1c082c423df3fe5c626532038bf

Here, the HMAC is a SHA1 hash of the session id 123123123banana with the secret key hunter2 (please pick a better key) that the server can use to re-compute the associated HMAC and determine the value hasn’t been modified.

Without the secret key, the attacker can not tamper with the message.

Make it so it doesn’t matter if they get tampered with

In the session fixation case, the most robust solution is to discard the user’s value for the session id cookie and provide a new one regardless if they already have one or not.

If you always get a new one, it doesn’t matter if your old one’s broken.

Don’t use cookies


I’m not even kidding.

This bug’s been around for close to a decade, with no sign of going away anytime soon. If you can, consider using headers to carry the same information since they’re much more difficult to modify on the fly or access through JavaScript and general injection attacks (HTTP Smuggling excluded).

That said, this adds a significant amount of engineering work since there’s no easy way to e.g. add headers to HTML form requests, and pros/cons should be weighed heavily.


Thanks for reading!

Sam

Share on

Sam Anttila
WRITTEN BY
Sam Anttila
Information Security Engineer @ Google. Opinions are my own and I do not speak on behalf of my employer.