XSS Part 4 – How XSS could be used to gain RCE (Remote Code Execution)

In my previous post we showed how XSS can be used to hit endpoints in the context of the logged in user and the damage that could be done with this.

The er great thing about security issues is that whilst they can be bad enough individually they can get really bad when combined together.

In this post I will show a theoretical example using my deliberately vulnerable application hackthecat where we’ll pair XSS with another security issue to end up running arbitrary code on the web server (Remote Code Execution or RCE).

RCE is pretty much as bad as a security issue can get for an application – especially if it’s possible without login details!

Obligatory Warning/Nag

Do not under any circumstances try any of what I’ll discuss here on applications you don’t have permission to. It’s almost certainly illegal and you could/will end up in a lot of trouble..

A much better idea with less jail/police is to use something like my deliberately vulnerable node application that you can download from https://github.com/alexmackey/hackthecat or a machine provided by a service such as https://www.hackthebox.com/.

Previous Posts

This is my 4th post on a series of posts about XSS but if you’ve missed the previous posts here’s a helpful list so you can go back and read them and hopefully they’ll assist you with preventing XSS issues within your applications:

Part 1 – https://blog.simpleisbest.co.uk/2022/02/19/xss-cross-site-scripting-part-1-what-is-xss/

Part 2 – https://blog.simpleisbest.co.uk/2022/04/02/xss-part-2-attack-and-defence/

Part 3 – https://blog.simpleisbest.co.uk/2022/04/25/xss-part-3-session-riding-csp-policies-sameparty-and-samesite/

De-serialization Vulnerability

OK but how could an XSS issue lead to running any old code we like?

We’ll get to that but first I need to introduce you to a well-known deserialization issue in a very old library that is no longer maintained called node-serialize, version 0.4 (scarily at the time of writing npm page says it had nearly 1400 downloads last week which er let’s hope is not in production systems).

Node-Serialize’s purpose in life is surprisingly enough to serialize/de-serialize objects to and from JSON.

It has an interesting feature where it can serialize functions on an object.

Unfortunately it also offers the ability to execute the serialized function during the deserialization process by making an eval call.

This only happens if the functions property name begins with the string “_$$ND_FUNC$$_”. The below screenshot shows how this works (FUNCFLAG is defined as “_$$ND_FUNC$$_” earlier in the code):

https://github.com/luin/serialize/blob/c82e7c3c7e802002ae794162508ee930f4506842/lib/serialize.js#L76

This logic means that if we can feed some malicious JSON that ends up in a node-serialize deserialize call then we should be able to get node-serialize to execute it!

Note you can checkout a simple example of this particular issue and write up at https://snyk.io/test/npm/node-serialize and https://www.exploit-db.com/exploits/50036.

Writing our exploit for HackTheCat

It just so happens that HackTheCat has an endpoint that accepts a blob of JSON (I know as I put it there!) and you guessed it uses node-serialize to deserialize it.

In our example application imagine an endpoint intended to create products from a JSON request. Providing valid JSON will then create a new product in Products table.

This is what the end point looks like:

https://github.com/alexmackey/HackTheCat/blob/main/web/routes/adminRoutes.js#L45

Using XSS we can execute a call to this endpoint and hopefully run some code on the server!

But why do we need XSS to do this?
Well we might not if the endpoint is unprotected and can be called by anyone but in the real world it’s more likely an endpoint like this would require an authenticated user with higher level privileges.

An attacker might use XSS in the hope a user with higher level privileges will stumble upon and trigger this endpoint.

Keep things simple

I quickly found that when playing with stuff like this it’s a good idea to do the simplest thing possible first and then build upon this.

If we were to try something more complex first like making a connection back to the attacker machine and it fails we don’t know whether this is because our approach was wrong or maybe there are other things such as firewalls preventing the connection.

We’ll first try to create a file using the touch command (Linux command that creates/modifies/sets properties on a file – this won’t work on Windows unless you have WSL running).

Some quick notes

I’m going to use a Linux based machine for this example as both the attacking and victim machine (yes we’ll connect back to ourselves but the approach is the same on distinct machines).

Ok we want to do the following:

  • Run the code when the page has loaded so we’ll add an event listener (not strictly necessary for this but other XSS attacks may require DOM elements to exist/be loaded)
  • We need to pass a valid product object that the endpoint expects just in case there is logic that expects various properties to exist (that’s the productName, smallImageUrl bit in the code below)
  • Pass any credentials the user may have (in the real world this is the sort of endpoint which is hopefully authenticated). We’ll use the credentials “include” option in our fetch request
  • We want to know when our call has completed so we’ll also make a call to http://localhost:8000/callback when the request has been made. This would also be useful if we were scripting the whole attack

Below is some dodgy code I wrote to do this:

Now once you’ve written something similar you’ll need to do the following to trigger it:

Log into hackthecat application

Use Netcat on your attacker machine to listen to port 8000 for the callback that will occur once the script has run (nc -lvnp 8000)

Next go to the products page, add a comment section and enclosing the XSS payload within a script block so it executes (e.g. <script>code here</script>) and then submit the comment.

All being well you should then see that your callback endpoint has been hit:

You should then find a file created on the server:

Oooo its looking good but what else might an attacker do?

Reverse Shells

Whilst the goals of attackers will vary having interactive/command line access to a machine is going to open a lot of possibilities from exploring files/services on the victim machine to using the machine as a stepping stone to attack other servers on the same network so it’s likely to be many attackers end game.

Most web servers will likely have a firewall or network rules between them and the Internet (or should do) that restrict access to specific ports or traffic such as 80 and 443.

These rules however are generally a lot less restrictive the reverse direction e.g. from the web server to the internet and also certain ports will likely need to be open for the server to do anything useful.

A reverse shell connects a pipe from the server/victim to the attacker allowing the attacker to send commands of their choosing.

There are lots of different ways of creating a reverse shell depending on the operating system, framework used, CPU architecture etc but in our example we know we’re working with a Node application and can execute node code so we’ll use a node reverse shell (I found this particular one by googling “node reverse shell” but there’s a great list here: https://book.hacktricks.xyz/shells/shells).

You can read more about reverse shell’s here: https://www.acunetix.com/blog/web-security-zone/what-is-reverse-shell/

Getting a reverse shell via hackthecat

Before we tell the victim machine to connect to us we’ll need something for it to connect to so let’s use Netcat to listen in on port 9000 for a connection (if you are trying this on two separate machines remember to check any firewall settings and allow access to the listening port from the victim machine):

Next we’ll use the same code we used in our example above but instead of using the touch command we’ll run a node reverse shell to 127.0.0.1 port 9000:

If you then follow the same process as above example you should find you get a connection back! (yes I’m aware I’ve connected to myself but didn’t want to setup another machine – change these details as per your own setup..)

This is interactive so you can run various commands – lets run pwd & id (note you’ll want to upgrade this to a full shell but that’s another topic entirely):

Summary

We’ve shown how an attacker could use an XSS issue, combine it with a deserialization issue to end up running code on the host of the application and even get an interactive session on it!

This is also a realistic issue – here’s the first real world example I found from a quick Google: https://blog.sonarsource.com/magento-rce-via-xss

Well hopefully by now I’ve convinced you that XSS can be really bad but what should we do to prevent this?

  • Always encode output then the XSS attack would not have worked in the first place
  • Ok this is not XSS but we couldnt have done the RCE without this – monitor the third-party components you use – npm flags that node-serialize has a known security issue and third-party solutions such as Synk and Whitesource will too
  • Validate and filter input from untrusted sources – do you need to be able to accept HTML/JS from a comments textbox – probably not but note its pretty hard to do this as we’ll see shortly
  • Define CSP policies that allow just the minimum functionality necessary for a page – if an attacker found a way round our encoding/filtering this could prevent or limit what they can do
  • Use HTTPOnly and secure option on session cookies to make the attackers life harder and prevent session hijacking/interception
  • Transmit scripts and cookies over secure connection
  • Have a firewall/network restrictions between your server and internet (duh)
  • Restrict outbound connections from the server to just what is needed – more advanced firewalls that can inspect traffic may have prevented suspicious connections like our reverse shell
  • Don’t run web applications as root/high privileges – if in our example the hackthecat application was running as root when we’d performed this attack then attacker has free reign on the machine. If they were able to perform this attack as a lower privileged user they are going to have to find a way to elevate their privileges if they want to do much
  • The script we used was actually quite long and having field length restrictions/validation would have made the attackers job harder

Next Post

Some folks believe that they can filter out XSS attacks with simple string replacement approaches taking out script tags and event handlers.

In my next post we’ll look at some of the literally millions of ways of creating XSS attacks and explore some weird corners of HTML and JavaScript.

XSS Part 3 – Session Riding, CSP policies, SameParty and SameSite

In my previous post XSS Part 2 Attack and Defence we looked at how an attacker can use XSS to steal a users session token from a cookie and then hijack it.

We then enabled the HTTPOnly option on our session cookie preventing malicious JavaScript from accessing the cookie value and sending it to the attacker.

Whilst HTTPOnly option will prevent this scenario unfortunately there is a another we need to address.

Obligatory Warning

Before we go any further obligatory disclaimer/warning/nag – do not perform any of the attacks we’ll discuss on sites you do not have permission to otherwise you could get into a lot of trouble or even jail.

A much better option to explore this topic is to use something like my deliberately vulnerable application https://github.com/alexmackey/hackthecat or HackTheBox.

Back to the content..

Ok where we?

Ah yes so the big issue we need to address occurs because if an attacker can execute code on a web page then it will operate as if it was legitimate code written by the site author with the same access to cookies, local and session storage.

Now browsers under most circumstances will automatically send cookies set by the site the user is browsing with any requests made via XHR & fetch requests (note that fetch requests require this to be explicitly enabled).

This can be a big issue when an XSS problem exists as because if a cookie value is used to identify a user (like in our example) and an attacker can get code to run on a page then they can call any API endpoint in the context of the logged in user!

Uh oh..

An attacker could then use this to:

  • Hit an API to change the user’s password to a known value (we’ll do this in a minute!)
  • Perform any valid action the user can – maybe this is transferring money in a banking application or purchasing products and sending them to the attacker or maybe exploiting some dumb Web3 NFT thing..
  • Perform additional XSS attacks but as the user themselves e.g. messaging other users XSS nastyness
  • ..and a thousand other horrendous things!

So how does this work?

Creating this attack is trivial and any web developer will have written similar (but probably better) code for completely legitimate purposes.

Below is an example of code that in the hackthecat application will change the password of the logged in user.

If we can get this to run in a page the user viewing it will have their password changed!

<script>
var formData = new FormData();
formData.append("username", "user");
formData.append("newPassword", "changed!"); 
formData.append("confirmNewPassword", "changed!"); 
var request = new XMLHttpRequest();
request.open("POST", "/user/change-password");
request.send(formData);
</script>

You can of course write this using fetch as well just remember you’ll need to use the credentials option set to “include” or “same-origin” otherwise fetch wont send credentials/cookies.

To see this in action yourself simply log into the hack the cat application (user and pass will do), go to a product and add a review with the above code.

When this runs you’ll then find this has changed the users password – here’s an exciting before and after mysql query screenshot – note how the password is “changed!” in the second query:

Local/Session Storage and Session Tokens

Some sites use local or session storage to save a users session token and then append it to requests rather than storing tokens in a cookie.

I think generally as the XSS code is executing in the context of the user this approach is unlikely to provide any additional protection. In fact it could make things worse.

I’m of two minds about storing session tokens in local storage. Whilst you are arguably in trouble if an XSS issue exists (due to the attack type we are currently discussing) you lose the benefit of how cookies work and expire and additional protection such as HttpOnly or SameSite options that are baked into modern browsers.

CSRF (Cross Site Request Forgery) Tokens

Some of you might be thinking oh this is where CRSF tokens come in – they’ll save me!

CSRF tokens are intended for preventing a different issue (you can read more about them here: https://portswigger.net/web-security/csrf/tokens).

CSRF tokens are likely implemented as a special value stored in a hidden form field that is sent with any requests and compared server side.

To circumvent this protection will require the attacker to read a value of a hidden form field which er really isn’t too tricky so in respect to this attack doesn’t really provide any protection (except against some very lazy attackers).

You should still however use CSRF tokens as they will of course offer protection against CSRF attacks – exactly what they were intended for.

What can we do to prevent this attack?

Apart from preventing XSS issues in the first place probably the most effective protection against this particular attack is to ensure that critical actions such as changing a password enforce the user to provide their original password first as hopefully the attacker and script will not have access to this.

I guess you could also implement a CAPTCHA but no-one wants to count how many Volvo’s are in a crappy picture displayed in a 16×16 grid – no they really don’t – do better.

We are however going to make the attackers life a lot harder by using Content Security Policy (CSP to its friends).

CSP headers (Content Security Policy)

CSP policies allow the client application fine grained control of what should and should not be running on a page and from where.

CSP policies are supported in every major browser and in a limited form in IE10+ (which hopefully you don’t have to support).

CSP policies can be set either in HTTP headers or meta tags and look something like this:

Content-Security-Policy: script-src https://simpleisbest.co.uk/

The above policy says scripts can only run from simpleisbest.co.uk and some other behaviours I’ll discuss shortly.

Below is the meta tag equivalent:

<meta http-equiv=”Content-Security-Policy” content=”script-src https://simpleisbest.co.uk‘ ‘self’;”>

What can you do with CSP policies?

Let’s say we have set the following CSP header on my site simpleisbest.co.uk:

Content-Security-Policy: script-src https://simpleisbest.co.uk/

Adding this header to HTTP requests will do the following:

  • Any inline scripts will be blocked (unless explicitly enabled with ‘unsafe-inline’ option) – this would prevent the XSS attack we have been discussing!
  • Inline event handlers e.g. onclick=”doSomething()” will be blocked preventing another attack vector
  • If the attacker tried to load a script from anywhere but simpleisbest.co.uk it’s going to be blocked
  • Several script execution methods such as eval(), Function, setTimeout, setInterval, setImmediate are blocked unless specifically enabled with unsafe-eval option
  • It prevents an attacker sending data to another server with html forms

Now the chances are when you first implement CSP policies especially on a site that has been around for a while some of these settings will break the site – you’ll find all sorts of dubious approaches in your code and perhaps third-party libraries doing some weird stuff!

Whilst you are first implementing CSP policies I suggest instead of using “Content-Security-Policy” I suggest you use “Content-Security-Policy-Report-Only” – you’ll see stuff that CSP would have stopped in the browser console instead of it being blocked making it easier to fix it up.

Enabling specific inline scripts

If for some reason you wanted to enable specific inline scripts (really?) but benefit from the protections CSP offers you can do this by adding a nonce to your inline script and then specifying the nonce to be expected like so:

Content-Security-Policy: script-src 'nonce-test'
<script nonce="test">
…
</script>

Ensure only specific scripts can be loaded

CSP policies allow us to ensure that only specific versions of scripts can be loaded by specifying an expected hash of the script like below (remember to include script tags, whitespace and that this is all case sensitive):

Content-Security-Policy: script-src 'sha256-yourSha256hashhere'

This is probably a good idea if you are referencing any scripts especially those on third party servers. This way you can be sure what your users will get is exactly as expected!

The downside of course is that its going to require some maintenance when scripts and assets are updated but it’s a small price to pay.

Which policies should I use?

CSP policies offer a huge number of settings to tweak.

Ideally, you want to only allow the minimum necessary for your site to function – this will reduce the surface attack area of your site and make the attackers life really hard if you can limit what options they have to utilise.

OWASP list the following CSP policy on their cheat sheet page (https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html)

Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self'; frame-ancestors 'self'; form-action 'self';

This will do the following:

  • All resources must be in same domain as document
  • No inline script, evals for script and style
  • No need for other sites to frame
  • No form submissions to external sites
  • Prevents loading of non ajax and CSS resources

This seems a pretty good base to start from and adapt to your purposes.

Adding a CSP Policy to HackTheCat

Ok let’s add a simple CSP policy to hackthecat site so we can see the impact.

We’ll do this directly in homeRoutes.js.

Add the following line before the res.render call in homeRoutes.js:

res.set("Content-Security-Policy-Report-Only", "default-src 'self'");

Note really you want this header to apply to every response so this isn’t the best way to do this as it will only apply to this endpoint. Also note that there are some third party express libraries to do this.

Once you’ve added this, restart the app, navigate to the page and you will then see something like the following in the browser console showing the inline script has been blocked:

Changing this to “Content-Security-Policy” mode will also display information about the referenced stylesheet hosted on fonts.googleapis.com being blocked as we haven’t said this is OK in our policy:

Deprecated approaches

You may come across a reference to various other XSS protection headers/options such as

  • X-XSS-Protection
  • X-Content-Security-Policy
  • X-Webkit-CSP
  • X-FrameOptions

These are all now deprecated and superseded by CSP Policies (for X-FrameOptions you can use frame-ancestors to provide protection against frame based evilness).

Using these legacy approaches can even cause security issues so don’t use them.

SameSite and SameParty

Before we wrap up this post I just wanted to tie up a loose end from the last post on SameSite and SameParty cookie options that you may have seen in the browser dev tools:

SameParty is a proposal designed to deal with the issue that modern sites are often served over multiple domains owned by the same entity. The intention is to allow developers more control over the privacy boundaries of where cookies are shared.

As this is still a proposal I’m not going to worry too much about it at this stage but its an interesting idea. You can read more about this proposal here: https://chromestatus.com/feature/5280634094223360.

SameSite however is ready to use now and supported in all major browsers apart from our old friend IE11.

Same-site is designed to protect against CSRF and potential information leakage by allowing developers to control that it should only be sent from requests initiated by the domain it was created in.

This is different to the default cookie behaviour where cookies are automatically sent to the same domains that they were set in.

Same site has 4 main options:

  • Strict
  • Lax
  • None
  • Unspecified (behaves the same as lax)

What do these SameSite options do?

Let’s assume a scenario where:

The user has a cookie set by my domain simpleisbest.co.uk

The user is on an external blog site hosted at wordpress.com

Strict
If the cookie had SameSite set to strict then the following will occur:

  • If the blog site references an image on simpleisbest.co.uk then no cookies would be sent to simpleisbest.co.uk from this request
  • If the user were to follow a link from wordpress to simpleisbest.co.uk then on the first request they make no cookies will not be sent offering protection against CSRF attacks that rely on the user being logged in and clicking a malicious link
  • Once the user is on the site e.g. they click a link on simpleisbest.co.uk then cookies will be sent as they are now in a first-party context (e.g. on the site that set the cookie).

Lax
When SameSite is set to lax then:

  • If the blog site was referencing an image and requested an image from simpleIsBest.co.uk then no cookies would be sent as per strict mode above
  • Unlike strict mode however if the user follows a link to simpleIsBest.co.uk then cookies will be sent.

None

When SameSite is set to None:

  • If the blog site was referencing an image on simpleIsBest.co.uk then cookies would be sent with the request
  • If the user follows a link from another site to the site that set the cookie (simpleIsBest.co.uk) then cookies will be sent
  • Note you must set the secure attribute on cookies when using None

Unspecified

Unspecified now behaves the same as Lax (recent change).

Ok let’s wrap things up – in this post we looked at how XSS can without further protections in place allow us to execute actions in the context of the user such as change their password or call arbitrary endpoints via XHR or fetch.

We then looked at how CSP policies provide fine grained control of what functionality can run in a page and can prevent or at least make an attackers life a lot harder then finally we looked at SameSite and SameParty cookie options.

In my next post I’ll show how an XSS issue can be exploited to run arbitrary code on a webserver which is pretty much as bad as things can get!

XSS Part 2 – Attack! (and defence)

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 😦

To see if the review text is encoded we could add some simple styled HTML or a script block to write to the console or that old classic the native JavaScript alert box aw yeah.

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!

At this point we’ve added some HTML which whilst has possibilities around defacing the site, mis-information to promote dogs to our feline readers is not as useful or convenient as being able to run some JavaScript directly so let’s see if we can add a script block too.

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:

Some sites may allow some HTML tags to format input but filter out more risky things like <script> and JavaScript so you’ll probably want to play with both.

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:

HackTheCat uses a templating engine called EJS (https://ejs.co/) which stands for Embedded JavaScript templating.

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.

HttpOnly flag (first implemented in 2002 by Internet Explorer team in IE 6 SP1!) tells the browser that the cookie can only be access from HTTP and not by JavaScript on the page.

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!