Table of Contents
- Checking for known security vulnerabilities
- Auditjs
- Snyk.io
- Preventing brute force attacks or flooding by adding rate limiters
- Protecting against evil regular expressions
- Blocking cross-site request forgeries
- Tightening session cookies and effective session management
- Adding helmet to configure security headers
- Avoiding parameter pollution
- Securing transmission
- Preventing command injection/SQL injection
- TSLint/static code analyzers
- Addendum for Node.js 10 Upgrade
A microservice architecture shifts around complexity. Instead of having a single and very complicated system, there are a bunch of simple services with complicated interactions. The goal is to make sure that complexity stays in check and within boundaries. Security is really hard to get right.
There are countless ways to break into an application. Node.js is no different. In this article, you will look at techniques to prevent security vulnerabilities. This article is meant to act as a basic checklist to ensure that your microservice addresses some of the biggest security threats. You can check out the complete code for this article at https://github.com/PacktPublishing/TypeScript-Microservices/tree/master/Chapter10/.
Checking for known security vulnerabilities
Due to a wealth of modules available in npm, you can directly work on the application and rely on the ecosystem for ready-made solutions. However, due to huge modules, larger security vulnerabilities can occur at any time even for mature popular frameworks. In this section, you will look at some valuable tools that ensure no vulnerabilities are present in the packages that the application relies on even while updating:
Auditjs
This is a simple utility that audits an npm project using the OSS index v2 REST API to identify known vulnerabilities and outdated package versions. Using this is very simple:
- Install it as a dev dependency npm install auditjs --save-dev .
- Add audit scripts in the npm scripts:
1 |
scripts:{ … "audit":"auditjs" ...} |
- Run the npm run audit command. The full example can be seen in the extracted folder under the chapter 10/auditjsfolder.
Snyk.io
This is another module that you can use to vet any modules against the vulnerability database maintained by synk.io. The major advantage of this module is that you do not need to install this for auditing. This module can be used as a pre-check before using any third-party module:
- Install it globally - npm install snyk –g.
- Once installed, you need to authenticate it by hitting snyk auth.
- Once snykis set up, you can vet any module using synk test <module_name>.
The following are some useful commands:
snyk wizard | Finds and fixes known vulnerabilities in a project |
snyk protect | Applies patches and suppresses vulnerabilities |
snyk monitor | Records the state of dependencies so that whenever new vulnerabilities or patches are launched, you can be alerted |
Preventing brute force attacks or flooding by adding rate limiters
Brute force attacks are common and often serve as a last resort for the hacker. They systematically enumerate all possible candidates for a solution and check whether each candidate satisfies the problem statement or not. To protect against this kind of attack, you have to implement some kind of rate limiting algorithm, which will effectively block an IP address from making an outrageous amount of requests, thus blocking the possibility of accidentally crashing the application.
You can find the rate-limiting implementation under the Chapter 10/rate-limiter folder, where you can use the rate limiting algorithm with the Redis database. Now, follow these steps:
- Install express-limiter and redis:
1 |
> npm install express-limiter redis --save |
- Create the redis client and set express-limiter:
1 2 3 4 5 6 |
letclient=require('redis').createClient() .. let limiter=require('express-limiter')(this.app,client) .. //limits requests to 100 per hour ip addresslimiter({lookup:['connection.remoteAddress'],total:100,expire:1000*60*60}); |
- Now, run the program. It will limit requests to 100 requests per hour, after which it will start to throw 429: Too Many Requests.
Protecting against evil regular expressions
One of the most commonly occurring vulnerabilities is a poorly formed regular expression. A regex, if it takes exponential time when applied to non-matching inputs, is termed an evil regex and should be prevented. An evil regex contains groupings with repetitions, alterations with overlappings, and words inside the repeated group. Here’s an example: Regex : (b+)+, ([a-zA-Z]+)*,(a|aa)+.
All these regexes are exposed to the input bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb!. These repetitions can be a hindrance as it may take seconds or even minutes to complete. Due to the event loop of Node.js, the execution won't go ahead, which will effectively result in the server freezing as the application is completely stopped from running. To prevent such disasters, you should use the safe-regex tool (https://www.npmjs.com/package/safe-regex). It detects potentially catastrophic exponential time regular expressions.
You can check the source code in the safe-regex folder. You can also check the regex by typing node safe.js '<whatever-my-regex>'.
Blocking cross-site request forgeries
A common way to intrude in an application is by putting data into the application via unsafe sites through a common phishing technique known as cross-site request forgery. An intruder making a phishing attempt can initiate a request via a form or other input that creates a request for an application through inputs exposed by the application.
To harden the application against this kind of attack, you can use a CSRF token implementation. Every time a user makes a request, a new CSRF token is generated and added to the user's cookie. This token should be added as a value to the inputs in an applications template, and this will be validated against the token the CSRF library generates when the user sends information. NPM provides the csurf module (https://www.npmjs.com/package/csurf), which can be used in express middleware directly and you can play accordingly with the csurf token.
The focus of the secure use of cookies cannot be understated in an application. This especially applies to stateful services that need to maintain a state across a stateless protocol such as HTTP. Express has a default cookie setting that can be configured or manually tightened to enhance security. There are the various options:
- secret: A secret string with which the cookie has to be salted.
- name: Name of the cookie.
- httpOnly: This basically flags cookies so that they can be accessible by issuing a web server in order to prevent session hijacking.
- secure: This requires TLS/SSL to allow a cookie to be used only in HTTPS requests.
- domain: This indicates specific domains only, from which the cookie can be accessed.
- path: The path cookie is accepted from an application's domain.
- expires: The expiration date of the cookie that is being set. If a timely expiration is not available, the resource consumption will be very high and resources will never be freed.
In the following example, you can securely set cookies using express-session and thus have effective session management. You can follow along with the example under typescript-express-session:
- Clone first-microservicefrom https://github.com/PacktPublishing/TypeScript-Microservices/tree/master/Chapter02/first-microservice, and install express-session and @types/express-session.
- In express.ts, add the following code, which will make your application use cookies with the following secured parameters:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
this.app.use( session({ secret: 'mySecretCookieSalt', name: 'mycookieSessionid', saveUninitialized: true, resave: false, cookie: { httpOnly: true, secure: true, domain: 'typescript-microservices.com', path: '/hello-world', expires: new Date(Date.now() + 60 * 60 * 1000) } })) |
Adding helmet to configure security headers
The helmet module (https://www.npmjs.com/package/helmet) is a collection of 11 security modules that prevents a varying number of attacks against an express microservice. It's easy to use, as you just have to add two lines of code. Adding some basic configurations can help to protect the application against possible security mishaps. You can use helmet by simply adding this code:
1 |
this.app.use(helmet()) |
The source code for this can be found in chapter-10/typescript-express-session.
The helmet module has a whopping 12 packages that act as some middleware to block malicious parties from breaking or using an application. These headers include headers for helmet-csp (headers for content security policy HTTP header), dns-prefetch protocols, frameguards, hide-powered-by, hpkp, hsts, ienoopen, nocache, dont-sniff-mimetype, referrer-policy, x-xss protections, frameguard to prevent clickjacking, and so on.
Another option for securing headers is lusca (https://www.npmjs.com/package/lusca), which can be used in combination with express-session. An example can be found in the chapter-10 /express-lusca directory.
Avoiding parameter pollution
In Node.js, if there are no defined standards for handling multiple parameters of the same name, the de facto standard is to treat those values as an array. This is extremely useful because for a single name when the expected outcome is a string, it types changes to an array if multiple parameters with the same name are passed. If this isn't accounted for in query handling, the application will crash and bring the whole thing down, making this a possible DoS vector. For example, check http://whatever-url:8080/my-end-point?name=parth&name=ghiya.
Here, when you try to read req.query.name, you expect it to be a string, but instead, you’ll get an array, ['parth','ghiya'], which will bring down the application if not handled with care. To ensure that the application won't fail you, you can do the following things:
- Various policies implement polluting mechanisms differently; for example, some may take the first occurrence and some may take the last occurrence
- Use TypeScript types to validate the request. If the types fail, stop the request by giving parameters errors
- Ensure that parameters in HTTP GET, PUT, or POST are encoded
- Strict Regexp must be followed in URL rewriting
Securing transmission
If an application has any moving parts (HTTP methods such as POST, PUT, and DELETE) that include anything right from logging or sending a tweet which mutates the information from the client, using HTTPs is a vital implementation to make sure that the information isn't modified in mid-transit. Cost can be an easy excuse for not investing in an SSL certificate. But now there are new, completely free, SSL certificate resources, such as Let's Encrypt (https://letsencrypt.org/).
Also, a Node.js application should not be directly exposed to the internet, and SSL termination should be handled prior to the request coming to Node.js. Using NGINX to do this is a highly recommended option, as it is specially designed to terminate SSL more efficiently than Node.js.
We already published a number of useful guides about NGINX on these security topics, such as:
- How to install NGINX in CentOS or FreeBSD and configure it as a reverse proxy
- How to purge NGINX proxy cache
- NGINX reverse proxy cache configuration
- How to create a self-signed SSL certificate with NGINX
- How to prevent http request flood (DDoS, brute-force attacks and so on) using the NGINX request-rate-limit feature
- How to install NGINX in CentOS
- How to implement HTTP Security Headers in NGINX
And so on.
To effectively set an express application behind a proxy, refer to http://expressjs.com/en/4x/api.html#trust.proxy.options.table. Once the HTTP is set up, you can use nmap, sslyze, or OpenSSL to test HTTP certificate transmission.
Preventing command injection/SQL injection
An injection attack can occur when an intruder sends text-based attacks that exploit the syntax of an interpreter. SQL injection consists of the injection of a partial or complete SQL query through user input, which can expose sensitive information and can be destructive as well. Similarly, command injection is a technique that can be used by an attacker to run OS commands on a remote web server. Through this approach, even passwords can be exposed. To filter against these kinds of attacks, you should always filter and sanitize user inputs. Using JavaScript statements such as eval is also another way to open up a door to injection attacks. To prevent these attacks, you can use node-postgres if you are using postgres (https://www.npmjs.com/package/pg), which provides positional query parameters. Common techniques to defend against injection include the following:
- To escape SQL injection, one of the techniques that can be used is escaping user input. Many libraries provide this out of the box.
- Parameterizing SQL queries is another way to avoid SQL injection, where you create the using positional query parameters and fill in the positional query parameters with values.
- eval() with user input is one of the ways to inject commands and should not be used at all (in the next section, you’ll learn to write a linter, which will avoid this).
- Similarly, the express application is vulnerable to MongoDB attacks. Not explicitly setting the query selector will result in your data being vulnerable to a simple query.
You have db.users.find({user: user, pass: pass}), where user and pass are coming from a POST request body. Now being type-less, you can simply pass query parameters inside this query, such as {"user": {"$gt": ""},"pass": {"$gt": ""}}, which will return all users along with their passwords. To resolve this, you need to explicitly pass the query selector, which will make your query db.users.find({user: { $in: [user] }, pass: { $in: [pass] }}).
TSLint/static code analyzers
In this section, you’ll look at one of the ways to analyze all the written code and check it against a list of security vulnerabilities. You’ll include this as one of the stages of your deployment plan. You’ll write a linter and have a .tslint file, where all the rules to be checked against are mentioned, and then you can run the lint.
TsLint is one way to check and validate the source code. It is a static analysis code tool that runs on Node.js in order to keep your source code clean, find possible bugs, uncover security issues, and enforce a consistent style across all your teams:
- Clone your first-typescript-microservices from earlier and inside it, add the following commands:
1 |
> npm install tslint --savenpm install tslint-microsoft-contrib --save |
- Next, write jsonwith the basic rules that you want to evaluate it against. Copy the rules from https://github.com/Microsoft/tslint-microsoft-contrib/blob/master/recommended_ruleset.js.
- Next, write an initialization script:
1 2 3 4 |
"analyze":"node ./node_modules/tslint/bin/tslint -r node_modules/tslint-microsoft-contrib/ -c tslint.json -s src/**/*.ts" |
- You can now leverage this script anywhere because when you run this, it will throw an output of all the errors found while evaluating against that rule set.
- You can add a --fixflag in the preceding script, which will automatically take the necessary measures in most cases. You can find the source code under the chapter 10/tslinter
Addendum for Node.js 10 Upgrade
NPM 6 gives protection against insecure code, which is used by more than 10 million JavaScript developers across the world to download more than 900 million packages of reusable modular code per day. These new protections include automatic alerts if anyone attempts to use open source code with known security issues and another option for npm audit, a command which allows developers to analyze complex and interdependent code to pinpoint specific vulnerabilities in the code.
Whenever you install npm, you will get the following alert if there is a security issue:
Whenever you npm audit afterward, you will get a detailed report like this:
You can further fix by running command npm run audit fix.