Setting the ‘Referer’ Header Using JavaScript
Or, “I’m Sorry, You Said You’re from Where Again?”
In a prior webinar on creating weaponized Cross-Site Scripting (XSS) payloads, I mentioned that XSS payloads (written in JavaScript) could not change the HTTP Referer header. Malicious requests made through an XSS payload will often have an unexpected Referer header that does not generally make sense in the normal workflow of the application.
Fortunately, the application used in that particular demonstration was not checking the Referer header. Our malicious payload worked even though the Referer was wildly incorrect.
It turns out that I lied to you dear readers—not intentionally of course.
It turns out that you can, in fact, set the Referer header using JavaScript with a simple trick that I was not aware of at the time.
But let’s backup a second. What is this Referer header, and why do I keep misspelling it?
The Referer header is set by your browser and sent to the server when you request a page. The value of this header is the URL of the previous page that linked to the newly requested page. It is where you came from, essentially.
And Referer is misspelled because it is misspelled in the actual RFC itself back in 1996—that is totally not my fault. Let’s see what this looks like in practice.
When the user clicks to visit the Archives section for December 2019, the actual request sent by their browser sets the Referer to the location from which they came. In this case, the Referer is set to http://192.168.78.157.
Now that we are on the Archive page, if we follow a link on that page, our Referer will be set to the Archive page.
And once we have clicked on the Who Wore it Best? Post, we see the request has the Archive page set as the Referer.
So why would we, as attackers, have any interest in controlling the Referer value? Let’s look at an example from the weaponized XSS webinar.
We have a vulnerable web application into which we have been able to inject malicious JavaScript through an XSS vulnerability, and we can make requests to the application using our target’s session. Our target is a site administrator in the demonstration, and we will use their level of access to add a new administrator to the site.
Before we run that exploit, let’s look at what a normal request to add a new administrator looks like. Below is the form the administrator fills out to add a new user.
When this form is filled out with the new user information and submitted, the request made has the Referer set to http://192.168.78.157/wp-admin/user-new.php, which is the location of the form.
We can make this same request from our malicious JavaScript to add a new administrator; however, the Referer will be set by the target’s browser to be the page where we were able to inject our malicious JavaScript. The JavaScript function we are going to use in our payload to add a new administrator can be seen in the figure below:
When this JavaScript runs in our target’s browser, a request is made to add a new administrator user, with a password that we (the attacker) know. In this example, our malicious JavaScript is loaded from a blog post preview page, where the particular XSS vulnerability we are exploiting places our payload. This results in the following request, with the Referer set to the post preview page, not the new user page as a real request would.
This incorrect Referer value is highly suspicious, because there is no reason that a new user add action should be coming from the blog post preview page.
Fortunately, in our demonstration application, WordPress is not checking the Referer values. But a recent discussion with a friend on this very topic revolved around an application that was using checks against the Referer value as a security control. And the code example below helped him bypass that security control.
So how do we get control of the Referer header in JavaScript? We could attempt to set the header in our JavaScript by adding the following code:
But the browser will not let us do this:
We could attempt to directly set window.document.referrer, but this does not work either.
Fortunately, this incredibly simple trick does it:
The result of changing the history entry before sending the request results in a modified Referer value:
There is a downside to this approach, however. The target may notice the side effect of this technique: the URL of the page hosting our malicious JavaScript has actually changed to our desired Referer value. The browser does not actually load this page. In fact, the example below does not actually exist, but the URL is modified for the current page.
A particularly observant user may note the unusual behavior of the page. Fortunately for us, JavaScript is pretty darned fast. We can easily use the history entry to change the URL, make our malicious request, and then change the URL back before the user notices.
Before we change the user’s URL, let’s grab a copy of the current location and save it. That way, we can restore the URL when we are done with our shenanigans. Let’s print out some values to the console to see what we need to store in order to fix the URL when we are done.
Looking at the values printed out at the console reveals that we want the pathname and the search values to be saved if we are going to properly restore the URL when our attack is complete. Alternatively, you could regex the href property as well.
The code below grabs the current URL, changes it to what we need it to be for our desired Referer value, and then changes it back after we have added our new administrator. The user will never notice the URL change.
The fruits of our efforts give us the Referer we desire:
And the URL switches back instantaneously before the user can notice:
So that is a simple technique for controlling the Referer value from JavaScript. This will not be useful often. Few applications will check the Referer value as part of a security control, but they are out there. An even less common avenue to apply this trick is for an application that reflects the Referer value (e.g., 'go back' link), potentially opening the door to an XSS vulnerability.
These are often easy to demonstrate through Burp Repeater by manually setting the Referer value with a JavaScript payload. However, practical exploitation is not likely as all modern browsers will URL encode the Referer value before it is sent, breaking XSS, SQLi, etc. payloads. If you find an application that reflects the Referer header that also, for some inexplicable reason, URL decodes the Referer value, you would have an exploitable reflected XSS vulnerability.
If you are still with me, thank you for allowing me to clear up errors in some of my previous blog posts regarding the ability to control the Referer value. If you wish to know more about the malicious payloads used in this post's demonstration, please see my previous blog post and webinar on weaponizing XSS payloads:
Blog: https://www.trustedsec.com/blog/tricks-for-weaponizing-xss/
Code Samples on GitHub: https://github.com/hoodoer/WP-XSS-Admin-Funcs
Code Example: https://gist.github.com/hoodoer/c4eb12b99d5902119fb30e8343b5b228