In my first post we looked at what XSS is, the different types and summarised some of the potential impacts it could have.
In my next few post’s we’re going to explore common XSS attacks and how we can prevent or mitigate them.
As always don’t try this stuff on sites you don’t have permission to as its almost certainly illegal and you could get into a lot of trouble..
A much better idea is to use something like my deliberately vulnerable node application that you can download from https://github.com/alexmackey/hackthecat and that’s exactly what we’ll be using in this post.
We’ll start off with some basic stuff and build upon it over the coming posts.
Finding XSS Issues
First of all, an attacker will need to locate an XSS issue in your application.
XSS issue can occur under the following conditions:
- There is input controllable by an attacker
- The input appears somewhere on a page that will be processed by a browser (or something browser like that will execute HTML/JS)
- The input is not adequately validated/sanitised allowing tags such as <script> (note there’s many options here so do not think XSS is confined to just the script element)
- When the input is eventually output to the page it is not encoded
- Other protections such as CSP are not in place. We’ll talk more about CSP in future but for now know that CSP can prevent or mitigate some XSS attacks
Finding all the places XSS can occur in a large application can be time consuming and difficult so there are many different commercial and free engines that automate this process. These range from the freely available tools such as OWASP ZAP to affordable options such as Burp Professional up to tools costing thousands of dollars (and more).
A tool likely wont find everything and some XSS issues will require multiple steps to occur for it to manifest.
An example would be an XSS issue existing in a user registration form where everything is done right however elsewhere the application outputs the username in an unencoded form leaving an opportunity for XSS to occur if an attacker were to enter a malicious username.
However I digress and in our example we don’t need any of these automated scanners and we’ll find the issues manually by trying to add some HTML and see if it is output encoded.
Browsing around the HackTheCat site we are running locally we come across a page that allows users to add a review to a product.
This page is probably a good candidate to test as we can control the review text and its likely to be output to this page:
We wont know however whether this page is vulnerable to XSS until we try entering some values. It may be that the text we’ll enter has been encoded properly or sanitised on the back-end which will put a stop to our evil plans 😦
I guess a real attacker would probably try something subtle first to avoid alerting the team looking after the site.
In our HackTheCat cat bicycle shop example we’ll add some simple HTML in our review text:
<h3>Cats suck! Dogs Rule!</h3>
We’ll add this text and sure enough we see some larger formatted text so it’s likely this is not be encoded properly – we’ve found our XSS!
We’ll do this by adding a message to write to the console like below:
Now if we press F12 to bring up the developer tools we can see we have a message that has been written to the console:
Back to our example – as we can see via output in the console we’re in business to do much more devious stuff now which we’ll get onto shortly.
Before we get onto this let’s see how this issue occurs in the first place as its key to understanding XSS.
XSS issues can occur when we accept input from the user, do not sanitise it, don’t encode it and output it somewhere where it will be executed
If we do a View Source on the page we’ll see something like the below:
This looks just like we’d written this script block ourselves in the applications code – and it will run in the same way as if we had!
In our HackTheCat application the page responsible for displaying user reviews is detail.ejs:
In this instance there’s no problem with EJS engine – the problem is in the choice of methods used to output the product review.
Here I’ve deliberately used the wrong output method to demonstrate this issue.
EJS offers two main ways of outputting content <%- (outputs raw, un-encoded output) and <%= (encoded output).
Why would you need raw output?
We’ll for the most part you probably don’t but it could be needed for certain scenarios for example the EJS documentation mentions using it to avoid double encoding output or where you can be sure the content you are outputting is not malicious.
For most circumstances however when using EJS you’ll want <%= to avoid XSS issues.
So does using a framework encoding method like this mean you can avoid XSS?
Most of the time.
However there’s certainly been issues in templating engines, frameworks and browsers and likely will again. Just last week Chromium had a parser issue and there’s been several XSS/client side template issues in frameworks such as Angular.
I dont think we’ve seen the last of these either and as per defence in depth you probably want to consider sanitising input too.
In a later post we’ll look at what frameworks and templating engines are actually doing underneath the cover and the native DOM methods performing this work.
Anyway let’s change the code to use <%= and see the difference in behaviour and output:
If you are using the containerised version of HackTheCat remember to rebuild with the –build flag to get this change:
When you have made this change navigating to this page should now show something like the following and no message in the console:
If we view the source we’ll see it looks a bit different too as the special characters have now been encoded properly:
Compare this with the unencoded version:
Ok this seems pretty straight forward.
There’s certainly been (and will continue to be) issues like this but XSS is can be more subtle than this.
XSS can often occur in less obvious places with values being passed into dialogue boxes or perhaps from other locations such as a user registration form.
Before we move on lets change back the output method so our comment is unencoded as I want to show you some more issues and how we can mitigate them:
Cookie Stealing/Session Hijack
Another XSS attack you should be aware of is that if an XSS issue exists then it can be used to try and steal cookies and ultimately a users session (assuming user sessions are session cookie based).
There’s a lot of different ways & libraries to manage sessions but in our HackTheCat example I’m using express-sessions to manage a users session within the application and sessions are maintained using a cookie.
We can see this in action by logging into the application (by default credentials are username “user” and password “pass” or “admin” and “pass”).
If we bring up the dev tools (F12), select the application tab and look at cookies we’ll see something like the following:
Here we can see that express-sessions has set a cookie called connect.sid containing a random ID. This will be used to identify a user.
Now if we were to copy the value of this cookie and set a cookie with the same name on another machine (and no additional checks/protections were in place and the session had not expired) then as far as the web application is concerned we will be the same logged in user – try it using incognito mode if you want.
This means if an attacker can steal a cookie then they can act as the logged in user.
What’s even worse is if the user had higher privileges e.g. an admin user was browsing the site then the attacker could have access to administrative functions that could be very damaging.
This scenario can occur with XSS using a simple script (or we could just use an img element directly) like the following:
If you look in the browser network tools you’ll then see this session cookie float off to the attackers server where they can gleefully pick it up and pretend to be various users:
Now we can prevent this by making use of some settings on the cookie.
If you open up app.js you’ll see the following:
I’ve deliberately used some bad, unsecure options to demonstrate these concepts.
Luckily many frameworks have some good default options out the box to help keep your applications safe but dont assume, check!
The key option here to prevent this particular attack is that HttpOnly flag.
We can see this flag wasn’t set in the browser tools when we look at the cookie:
Lets set this option now by updating the session configuration in app.js:
In the real world you’d probably want to set that secure flag too to ensure the cookie is only sent over a secure HTTPS connection so it cant be easily intercepted but lets ignore this for now.
Once you’ve made this change if you now look at this cookie we’ll see a nice HTTP Only tick:
And if we then look at the page source we’ll see there is no cookie present:
Trying to access the cookie properties in the browser console will return nothing:
OK so we’ve prevented that attack and you may think you are out the woods by adding these additional protections but as I’ll demonstrate in my next post you certainly are not.
In my next post we’ll talk about SameSite and SameParty flags, the other bad stuff an attacker could do despite HttpOnly flag and then show how this can be chained with another issue to get RCE or remote code execution but for now that’s all folks!