XSS via HTTP Headers

In some cases, an information passed in one of the HTTP headers of the application is not correctly sanitized and it’s outputted somewhere in the requested page or in another end, giving rise to a XSS situation.

But unfortunately, once an attacker can’t make a victim to edit his/her own HTTP headers in an actual XSS attack, those scenarios can only be exploited if the attacker payload remains stored somehow.

The first situation that might come to our minds is the classic one: some info in HTTP headers we can control gets stored in a database and retrieved later in the same page, anywhere else in the application or even in another non-reachable system for the attacker (Blind XSS).

But there’s another pretty common situation nowadays due to CDNs and WAFs that makes possible to persist our attack without the need of that database step: web cache poisoning. That’s what we will see in this post.

Take the following exercise:


All of our request headers appear there in a JSON format. It’s an extrapolation, in real world scenarios it might appear just one or 2 of them. It’s just to make it easier since the code is an one-liner (PHP) one can easily reproduce:


As we can see below, using curl at command line with -i flag, it show us the HTTP headers of response along with our JSON conatining our request headers.



Because of the last header “x-sucuri-cache” provided by the WAF we use in this blog, we need to add something in the URL to avoid the cache, since the value of that header is “HIT” which means it is coming from WAF’s cache.



So by adding “lololol” we were able to retrieve a non-cached version of the page, indicated by the x-sucuri-cache header value “MISS”. Now we are going to inject our own header (with -H flag) to check if it appears back in response.



Success, our dummy header pair “Test: myValue” got reflected in response.  Let’s change our “cache avoidance string” to make one more request or this next one will return the last cached response with “lololol” string.



We used “kkkkk” as string in the URL to start the cache processing again. We also injected a XSS vector as we can see above. But just for us, since we are sending that header via terminal. It wouldn’t appear in a request done by a browser, from others and not even from us.



Another request is made (check time at “date” header) but it seems to make no difference. That’s because cache is based in a MISS-MISS-HIT scheme so the next one will work.



Bingo, we have it cached. We open our Brave browser now with our cooked URL and:



That URL will remain poisoned until cache expires.


In Twitter we shared a similar exercise in the following page:



It works pretty much like the one we just have explained. All one needs to XSS it is to guess the entry point.

The source code of the page follows, so we can easily spot the solution.





P.S.: KNOXSS will also support these XSS cases soon.