You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

141 lines
4.9 KiB

3 years ago
---
title: XSS Through the Referer Header
tags:
- Security
description: In this blog post, I document how I achieved reflected XSS though a malicious http header.
date: 2021-10-14
---
XSS is a vulnerability in which a malicious actor is able to run JavaScript in
an unsuspecting clients browser session. Normally, this is done via input fields
whose values are reflected back to the user without proper sanitisation.
In this blog, I demonstrate a method I recently used that injected the payload
via the Referer [sip] header.
Only do this on websites you own or have permission to do so no. It is illegal
in most places to do this without permission.
## Initial foothold
I found myself looking at a website whose input fields were all well sanitised,
getting ready to write a pretty boring report. However, I spotted a snippet of
JavaScript in an analytics script that looked something like this:
```js
<script>
{
...
"referer": "site.com/page"
...
}
</script>
```
That sparked my interest so I opened pappy and changed the referer header to `"
+ alert(1),"":"`.
BRILLIANT! That worked. The referer header was not being sanitised.
## Crafting the referer header
So, that is nice, but it would take some serious social engineering to convince
someone to intercept a request in burp / pappy / some other proxy tool, change
their referer header to our payload and then submit the request. Really we
needed a way to control that header ourselves. For anyone that doesn't know, the
[referer header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer)
holds the address of the page that makes the request. This basically means the
previous page if a link was clicked.
There is also a related header called [referrer-policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy)
that controls how much information is sent. I want to send as much as possible
so I manually set this to `unsafe-url` for my proof of concept.
```php
<?php
header('Referrer-Policy: unsafe-url');
?>
```
I am using php here because I know it. I'm not saying php is the best choice.
Use what you know and can use quickly for proof of concepts.
My initial thought was to simply use HTTP redirects to first redirect the user
to a page whose url contained my payload, and then from there to the vulnerable
page.
The code for that was:
```php
<?php
$payload='"+alert(1),"":"';
header('Referrer-Policy: unsafe-url');
if ( $_SERVER['REQUEST_URI'] == "/index.php" ) {
header('Location: //localhost:8081/index.php/' . urlencode($payload));
}else{
header('Location: https://vulnerable-site.com/page');
}
?>
```
That didn't work. After some searching I realised that the referer header is not
changed when an http redirect is followed. It does however if the url is changed
with JS.
```php
$payload='"+alert(1),"":"';
header('Referrer-Policy: unsafe-url');
if ( $_SERVER['REQUEST_URI'] == "/index.php" ) {
$location='//localhost:8081/index.php/' . urlencode($payload);
}else{
$location='https://vulnerable-site.com/page';
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Example</title>
<script>
document.location="<?php echo $location; ?>";
</script>
</head>
<body>
Example
</body>
</html>
```
So, after hosting this at localhost:8081, I was able to visit it in my browser.
If doing this in the real world, I would host it on a public server somewhere
and try and convince a victim to click a link.
In my case, the initial link would be to `//localhost:8081/index.php`. This
would then use JS to redirect the victim to to
`//localhost:8081/index.php/%22%2Balert%281%29%2C%22%22%3A%22`. We would then
use JS again to redirct the user to the vulnerable site. With the
referrer-policy header set to unsafe-url, the browser will set the referer
header to the url including our payload and trigger our payload. In this
example, we are doing `alert(1)`. That's pretty boring and obvious to the user.
However, in the real world, we could send another request back to our server
with the contents of document.cookie to steal the session, or prompt the user to
re-enter their credentials and send that to ourselves. Once you have
unrestricted XSS, account compromise is normally possible.
## Improvements
Many of you may have noticed ways to improve the payload. For example, I hard
coded localhost:8081, the payload and various other information. If you want,
feel free to improve it but this is supposed to be a proof of concept, not a
well build program. When you're making POCs, it's the one time you really don't
need to worry about coding well - it's about getting something that works in a
short time frame.
## Solution
This issue came about because http headers were trusted. If the site had
validated the http header, or encoded it in some way, this would not have been
possible. Always sanitise. Never assume that anything that comes in the request
is safe.