JavaScript is the most popular programming language used to build everything from backend servers to interactive front-facing web applications which make up 95% of the web as stated by w3tech. While Javascript is extremely popular because of its usefulness in building dynamic web applications, certain security concerns arise because of its popularity. Staying ahead of these security loopholes/vulnerabilities will help companies preempt common security fails and provide secure applications to their users.

In the course of this article, We will take a look at some of the most common JavaScript vulnerabilities and also learn the necessary steps to take in resolving them.

What are Common Vulnerabilities?

Most security vulnerabilities in javascript come as a result of end-user interaction. Malicious users can input query strings into forms to access or contaminate protected data. It is, therefore, a responsibility on engineering teams to add a validation middleware on user inputs.

Listed below are the most common JavaScript vulnerabilities:

Cross-Site Scripting (XSS) SQL injection (SQLi) Open-source vulnerabilities

Cross-Site Scripting (XSS)

One of the most common attacks involving web applications and JavaScript are Cross-site Scripting vulnerabilities which are included in the OWASP Top 10 web application security risks.

An XSS attack is a flaw in web applications which allow malicious users to execute JavaScript code on a website hence its popularity because almost every website requires JavaScript turned on in users browsers.

Cross-Site Scripting is primarily of two types namely:

Client XSS

Server XSS

Client XSS

This type of vulnerability occurs when untrusted data supplied from a remote location (mostly APIs) is used to update the DOM with an unsafe JavaScript call.

As an example, the following snippet below gets weather information from an untrusted API to display current weather information.

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>DEMO XSS</title> </head> <body> <p>Today's weather information reads: </br> <div id="temp"></div> </p> <script type="text/javascript"> let untrusted_ajax_res = document.write(location.replace("https://www.w3schools.com")); document.getElementById("temp").innerHTML=untrusted_ajax_res; </script> </body> </html>

The snippet above shows how malicious code from an untrusted API can be injected into the DOM.

To prevent client xss attacks, developers can install secure xss filters which can be used to sanitise inputs gotten from untrusted domains/locations. A snippet for a web use-case can be seen below:

<!DOCTYPE html> ... <script src="dist/xss-filters.min.js"></script> <script> let untrusted_ajax_res = document.write(location.replace("https://www.w3schools.com")); document.getElementById("temp").innerHTML=xssFilters.inHTMLData(untrusted_ajax_res); </script>

The xss-filters package must only be applied to UTF-8 encoded documents. HTML5 mode is also required.

Server XSS

This type of vulnerability occurs when untrusted data is included as user input and sent into the backend to be stored on the database. This data is used to compute a response to be sent back to the user which contains malicious JavaScript code when sent back to the user.

An example of a server XSS attack on an express based node server can be seen in the snippet below:

... app.get('/search', (req, res) => { const results = db.search(req.query.product); if (results.length === 0) { return res.send('<p>No results found for "' + req.query.product + '"</p>'); } ... });

The snippet above shows the definition of a products search route for http://www.domain.com. An XSS attack can be performed by passing javascript code as a query for the server like so:

https://www.domain.com/search?product=<script>alert(XSS:Gat Ya!!)</script>

Since there wouldn’t be any record like

<script>alert(XSS:Gat Ya!!)</script>

on the database, the server’s response will be:

<p>No results found for "<script>alert(XSS:Gat Ya!!)</script> </p>

After the server’s response is received and No results found for is displayed, the script is then injected into the DOM to give the output shown below:

Even though this doesn’t look very severe as this script doesn’t pose any serious threat of sort.

Attackers, however, can save malicious code as “comments” in blogs/social media sites. These so-called comments, when served to other users, is then injected into the DOM and performs the wishes of the attacker.

To fix this vulnerability we can install the xss-filter package using npm like so:

$ npm i xss-filters --save

After installing, we can now filter the user’s input in our server’s response like so:

... var xssFilters = require('xss-filters'); app.get('/search', (req, res) => { var productParam = req.query.product; const results = db.search(req.query.product); if (results.length === 0) { res.send('<p>No result found for "' + xssFilters.inHTMLData(productParam) + '"</p>'); } }); ...

Using the xss-filters packages comes with certain warnings that can be found on its installation page

SQL Injection

SQL Injection is a type of attack which makes it possible to execute SQL statements to control a database behind a web application.

An SQL Injection attack can be seen in the code snippet below:

... const db = require('./db'); app.get('/products', (req, res) => { db.query('SELECT * FROM products WHERE id = ' + req.query.id); .then((product) => { ... res.send(product); }) });

There are two things wrong with the code snippet above:

The user’s input which is not to be trusted is directly concatenated with the query string. The database query is built via string concatenation.

If the user passes in 10;DROP TABLE Users — as the req.query.id what will happen is that an ID of 10 will be passed, but right after an SQL query of DROP TABLE Users — is executed. This is proof that user inputs should always be treated as unsafe and as such, validated accordingly.

Although handling user input validation is outside the scope article, you can read about how to handle data validation in node.

To fix problem number 2, you can secure database query strings by writing prepared SQL statements. An example of a prepared/parameterized query in Postgres can be seen below:

db.query('SELECT * FROM products WHERE id = $1', req.query.id);

In the snippet above, the database client will pass in the req.query.id as the first argument.

Although the MySQL package for node doesn’t support parameterised queries, you can escape users input (i.e remove all special characters ) before it’s concatenated with the query string like so:

const query = 'SELECT * FROM products WHERE id = ' + connection.escape(req.query.id);

Open-source vulnerabilities

The JavaScript ecosystem is filled with a lot of open-source packages that make product development easier software engineers. Open-source packages help speed up development time as different packages brought together with some proprietary code can help companies/software engineers build a viable MVP. Though the time to market is shortened, these packages leave a huge deposit of security vulnerabilities which attackers can put in malicious code to steal/compromise users data.

Package managers (e.g npm) and security-focused companies have built tools and products that help software engineers scan and fix vulnerabilities in their projects.

In this section, we will discuss how to stay ahead of vulnerabilities using npm and bolt.

Using NPM

Package managers (e.g npm) helps manage to a certain degree these vulnerabilities from packages installed from npm by providing vulnerability reports and providing a fix for them.

When we run the $ npm audit command in our project directory, npm provides an audit report for all installed dependencies like so:

To fix these vulnerabilities, we will run the $ npm audit fix command. This command, unfortunately, can not fix all the vulnerabilities and most cases will require developers to manually install and update these packages.

Using bolt

Bolt is a free GitHub app which helps continuously scan repositories (public and private) for vulnerabilities in open-source packages and infers fixes. This is done by scanning repositories multiple (limited to 5 times a day) times and with each vulnerability found, a GitHub issue is opened to that effect. Reference links, vulnerability information and fix suggestions are also provided.

Conclusion

Building software with JavaScript poses a lot of security threat which often overlooked by developers unconsciously because the pressing need to continuously ship out new features.

Open-source packages make up a large percentage of the JavaScript ecosystem and are used by companies who provide proprietary products to consumers. By using security measures provided by package managers ( such as npm ) and security solutions, companies can stay ahead of the vulnerabilities which arise from using these open-source packages.

Further reads