DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • C# Applications Vulnerability Cheatsheet
  • Nginx + Node.JS: Perform Identification and Authentication
  • What Is SQL Injection and How Can It Be Avoided?
  • Strengthening Web Application Security With Predictive Threat Analysis in Node.js

Trending

  • Beyond ChatGPT, AI Reasoning 2.0: Engineering AI Models With Human-Like Reasoning
  • Internal Developer Portals: Modern DevOps's Missing Piece
  • Unlocking the Potential of Apache Iceberg: A Comprehensive Analysis
  • Apache Doris vs Elasticsearch: An In-Depth Comparative Analysis
  1. DZone
  2. Coding
  3. JavaScript
  4. 10 Node.js Security Practices

10 Node.js Security Practices

Using the top 10 OWASP security practices as a guideline, this article outlines specific examples of how each can be applied to Node.js.

By 
Anton Lawrence user avatar
Anton Lawrence
DZone Core CORE ·
Updated May. 26, 20 · Analysis
Likes (10)
Comment
Save
Tweet
Share
29.7K Views

Join the DZone community and get the full member experience.

Join For Free

Web application security is rapidly becoming a major concern for companies as security breaches are becoming expensive by the day. The Open Web Application Security Project (OWASP) is a non-profit organization dedicated to web security. OWASP has put together a regularly updated list of the top ten web application security risks.

In the course of this article, we will examine the ten secure practices in Node.js which are in line with the OWASP top 10 web application security risks.

  1. Use parameterized inputs to prevent injection attacks.
  2. Use multi-factor authentication to prevent automated attacks.
  3. Discard sensitive data after use.
  4. Patch old XML processors.
  5. Enforce access control on every request.
  6. Create fluid build pipelines for security patches.
  7. Sanitize all incoming inputs. 
  8. Scan application for vulnerabilities regularly.
  9. Secure deserialization.
  10. Sufficient logging and monitoring.

Use Parameterised Inputs to Prevent Injection Attacks

SQL\NoSQL injection attacks are one of the common attacks on the web today. Attackers often send in queries in the guise of user inputs which forces the system under attack to involuntarily give up sensitive data/information.

A sample scenario for an injection attack can be seen in the snippet below:

JavaScript
 




x
10


 
1
...
2
const db = require('./db');
3
 
          
4
app.get('/users', (req, res) => {
5
  db.query('SELECT * FROM Users WHERE UserID = ' + req.query.id);
6
    .then((users) => {
7
      ...
8
      res.send(users);
9
    })
10
});


The snippet above is susceptible to an injection attack because user input is not sanitised. If the attacker passes in 200 OR 1=1 as req.query.id, The system will return all the rows from the Users table because of the RHS (right-hand side) of the OR command which evaluates as TRUE.

A secure way of preventing injection attacks is by sanitising/validating inputs coming from the user. Writing a validation rule that only accepts a value of type int will help prevent an injection attack.

Using prepared statements or parameterised inputs can also help to prevent injection attacks because then inputs are treated as inputs and not part of an SQL statement to be executed.

Although the MySQL for Node package doesn't currently support parametrised inputs, Injection attacks can be prevented by escaping user inputs like so:

JavaScript
 




xxxxxxxxxx
1


 
1
...
2
 
          
3
let sql_query    = 'SELECT * FROM users WHERE id = ' + connection.escape(req.query.id);
4
connection.query(sql_query, function (error, results, fields) {
5
  if (error) throw error;
6
  // ...
7
});
8
 
          
9
...


Use Multi-Factor Authentication to Prevent Automated Attacks

Lack of authentication or broken authentication leaves a system vulnerable on many fronts which is why broken authentication is ranked as number two on the top 10 vulnerability list. Vulnerabilities due to broken authentication come as a result of weak password/session management policies implemented in applications. 

There are many things to consider when implementing authentication flows such as password (creation and recovery) policies, session ID management, retry policies, etc. In many cases, it is often much easier to leverage already existing solutions such as Okta, Firebase Auth, OAuth, etc.

Aside from leveraging authentication service providers, two-factor authentication (2FA) can be implemented in-app using speakeasy. Speakeasy is an npm package which helps in implementing 2FA for node application by generating one-time tokens.

JavaScript
 




xxxxxxxxxx
1
29


 
1
var speakeasy = require("speakeasy");
2
// generate ascii, hex, base32, otpauth_url
3
var secret = speakeasy.generateSecret({length: 20});
4
 
          
5
// generate 6 digit code based on base32 secret
6
var token = speakeasy.totp({
7
  secret: secret.base32,
8
  encoding: 'base32'
9
});
10
 
          
11
...
12
 
          
13
// verify token coming from client, will return True if tokens match
14
var tokenValidates = speakeasy.totp.verify({
15
  secret: secret.base32,
16
  encoding: 'base32',
17
  token: req.query.token,
18
  window: 6
19
});
20
 
          
21
...
22
 
          
23
// Calculate time step difference in seconds
24
var tokenDelta = speakeasy.totp.verifyDelta({
25
  secret: secret.base32,
26
  encoding: 'base32',
27
  token: req.query.token
28
});
29
 
          


The snippet above shows how to get started with 2FA using speakeasy. To get started with multi-factor authentication, you can check out Okta's developer’s blog.

Discard Sensitive Data After Use

Sensitive data exposure occurs when an application exposes personal data belonging to its users or members of staff unintentionally. According to OWASP, sensitive data exposure has been the vulnerability with the common impact. Attackers can steal secrets, hijack user sessions, steal user data as well as perform man-in-the-middle attacks.

Passwords, credit card data, health records and personal information which falls under privacy laws (e.g. GDPR) require extra protection and should be treated with care. Storing sensitive unnecessarily is highly discouraged as data not stored can't be stolen.

To avoid sensitive data exposure, making sure passwords are being encrypted/salted with strong hashing functions such as Argon2, Scrypt, Bcrypt e.t.c is advised. 

Also, enforcing HTTP strict transport security (HSTS) on TLS will prevent packet sniffing and man-in-the-middle attacks by allowing access to your app on HTTPS only. This will ensure user data sent from client-side to server-side and back is passed through secure and encrypted channels.

Shown in the snippet below is how to configure HSTS for node applications:

JavaScript
 




xxxxxxxxxx
1
11


 
1
// npm install hsts
2
 
          
3
const hsts = require('hsts')
4
 
          
5
const sixtyDaysInSeconds = 5184000
6
app.use(hsts({
7
  maxAge: sixtyDaysInSeconds,
8
  includeSubDomains: false
9
}))
10
 
          
11
...


Patch Old XML Processors

XML external entities attack is an attack in which XML processors are tricked into allowing data from external or local resources. Older XML processors by default allow the specification of an external entity (i.e. a URI which is evaluated during XML processing).

A successful XXE injection attack can seriously compromise the application which it's performed on and its underlying server. An example of an XXE attack can be seen below:

JavaScript
 




xxxxxxxxxx
1


 
1
<?xml version="1.0" encoding="ISO-8859-1"?>
2
   <email>johndoe@domain.com</email>
3
</xml>


The snippet above is the XML data containing a users email expected to be processed by the application.

JavaScript
 




xxxxxxxxxx
1


 
1
<?xml version="1.0" encoding="ISO-8859-1"?>
2
<!DOCTYPE foo [<!ELEMENT foo ANY >
3
<!ENTITY bar SYSTEM "file:///etc/passwd" >]>
4
   <email>johndoe@domain.com</email>
5
   <foo>&bar;</foo>
6
</xml>


The snippet above is a malicious xml file which has been tweaked by an attacker to fetch the /etc/passwd file located on the server hosting the application under attack. 

Disallowing document type definitions (DTD) is a strong defence against XXE injections attacks. 

This can be carried out on the libxmljs XML parser like so:

JavaScript
 




xxxxxxxxxx
1
14


 
1
//npm install libxmljs
2
 
          
3
var libxml = require("libxmljs");
4
 
          
5
var parserOptions = {
6
    noblanks: true,
7
    noent: false,
8
    nocdata: true
9
};
10
 
          
11
try {
12
    var doc = libxml.parseXmlString(data, parserOptions);
13
} catch (e) {
14
    return Promise.reject('Xml parsing error');


Enforce Access Control on Every Request

Broken access is one of the core skills of attackers given to them by loosely implemented access control policies on the application under attack. Broken access control is usually as a result of insufficient functional testing by application developers. 

One way to stay ahead of this vulnerability is by manually testing application modules which require specific user permissions.

Access control rules and middlewares are best implemented on the server-side as it eliminates the possibility of manipulating access permissions from the client-side by cookies or JWT (JSON Web Token) authorisation tokens. 

API rate limiting and log access controlling should be set up. This way admins are alerted when there are repeated failures and necessary steps can be taken to mitigate the attack.

The snippet below shows two URLs:

JavaScript
 




xxxxxxxxxx
1


 
1
http://domain.com/app/profile
2
 
          
3
http://domain.com/app/admin/profile


A broken access vulnerability can be seen if both URLs can be accessed without any authentication middleware guarding them. If a regular signed-in  user without admin privileges can visit the admin page, that in itself is another vulnerability.

Create Fluid Build Pipelines for Security Patches

Security misconfiguration vulnerabilities happen when web applications or servers are left unguarded or guarded with weak security rules. This vulnerability leaves many parts of the application stack (application server, database, containers, etc.) susceptible to malicious exploits. 

Soft build pipelines are a major entry point of security misconfiguration typed attacks as development/staging area credentials at times make it to production. This leaves the application vulnerable as development/staging are configurations to have loose security policies. 

As such, it's advised to keep all environments (development, staging and production) identical with different credentials and distinct access levels. 

Default user account passwords and default package settings also open up vulnerabilities in node applications as attackers can launch brute-force dictionary attacks against login forms using weak credentials. 

Default package settings, on the other hand, leave vulnerability breadcrumbs for malicious attackers tail.

An example can be seen in the snippet below:

JavaScript
 




xxxxxxxxxx
1
13


 
1
/** 
2
* This will disable the X-Powered-By header
3
* and help prevent attackers from launching targeted attacks for apps running express
4
**/
5
app.disable('x-powered-by');
6
 
          
7
... 
8
 
          
9
// Helmet disables X-Powered-By header and also sets other secure HTTP headers
10
var helmet = require('helmet');
11
app.use(helmet());
12
 
          
13
...


Sanitize All Incoming Inputs

Cross-Site Scripting (XSS) attack is one in which attackers execute malicious JavaScript code on client-side facing applications. This type of attack is of two types, client XSS and server XSS. For this article, we will focus on server XSS.

Server XSS occurs when untrusted input coming from the client-side is accepted by the backend for processing/storage without proper validation. Such data, when used to compute the response to be sent back to the user, may contain executable malicious javascript code.

An example of a server XSS attack can be seen below:

JavaScript
 




xxxxxxxxxx
1
11


 
1
..
2
 
          
3
app.get('/search', (req, res) => {
4
  const results = db.search(req.query.product);
5
 
          
6
  if (results.length === 0) {
7
    return res.send('<p>No results found for "' + req.query.product + '"</p>');
8
  }
9
...
10
 
          
11
});  


In the snippet above, a malicious user can easily inject the following code below as req.query.product:

JavaScript
 




xxxxxxxxxx
1


 
1
<script>alert(XSS in progress!!)</script>


This code will be executed and returned to the user as a failed execution as seen in line 7 above. The problem with this is, a malicious code is returned which capable of causing a lot of harm to the said application/system under attack.

To mitigate XSS attacks, application developers are advised to treat all incoming data from the user as unsafe and as such validate user inputs.

XSS filters can also prevent XSS attacks by filtering data send back the user like so:

JavaScript
 




xxxxxxxxxx
1
17


 
1
// npm install xss-filters --save
2
 
          
3
var xssFilters = require('xss-filters');
4
 
          
5
...
6
 
          
7
app.get('/search', (req, res) => {
8
  const results = db.search(req.query.product);
9
 
          
10
  if (results.length === 0) {
11
    return res.send('<p>No results found for "' +
12
       xssFilters.inHTMLData(req.query.product) + 
13
    '"</p>');
14
  }
15
});  
16
 
          
17
...


Regularly Scan Applications for Vulnerabilities

The JavaScript eco-system is swarming with open-source packages which make a huge part of the internet today. Keeping track with the versioning and licensing of these open-source packages is often difficult and this breeds outlets for attackers to sneak in malicious code into our applications.

We can keep track of open-source vulnerabilities in our node projects by using the following:

  • Package manager provided solutions
  • WhiteSource Bolt

Package Manager Provided Solutions

Package managers such as NPM and Yarn helps application developers to block these outlets by locking versions of packages installed to prevent unwanted package updates. 

This in itself can cause more harm than good in the sense that outdated packages may contain vulnerabilities and since the  package-lock.json  file prevents unsolicited package updates, you'll miss out on install patches which fix these vulnerabilities. 

However, by running the   $ npm audit   in our project directory, npm can provide us with an audit report for all installed dependencies.

To fix vulnerabilities from running an audit, you can run   $ npm audit  fix. This command, however, can't fix all vulnerabilities and will sometimes require manual updates to be done.

Secure deserialization

Insecure deserialization is a flaw that involves deserialization and execution of malicious objects via API calls or remote code execution.

This type of attack can occur in two ways:

  • An object/data structure attack which involves the attacker modifying application by executing remote code on application classes which change behavior during serialization.
  • This is when legitimate data objects (e.g cookies) are tempered by the attacker for malicious intent.

In order to mitigate/prevent such attacks we will have to prevent cross-site request forgery (CSRF). This can be done by generating a CSRF token from our server and adding it to a hidden field form field. 

When said form is submitted, the CSRF middleware checks if incoming token coming matches what was sent before. The middleware will reject request where tokens don't match and accept those that do. This will curb remote code execution as well as legitimate object tempering.

Below is a snippet on how to mitigate cross-site request forgery using CSURF:

JavaScript
 




xxxxxxxxxx
1
31


 
1
var cookieParser = require('cookie-parser') // npm install cookie-parser
2
var csrf = require('csurf') // npm install csurf
3
var bodyParser = require('body-parser') 
4
var express = require('express')
5
 
          
6
// setup route middlewares
7
var csrfProtection = csrf({ cookie: true })
8
var parseForm = bodyParser.urlencoded({ extended: false })
9
 
          
10
// create express app
11
var app = express()
12
 
          
13
// parse cookies
14
// we need this because "cookie" is true in csrfProtection
15
app.use(cookieParser())
16
 
          
17
app.get('/form', csrfProtection, function (req, res) {
18
  // pass the csrfToken to the view
19
  res.render('send', { csrfToken: req.csrfToken() })
20
})
21
 
          
22
app.post('/process', parseForm, csrfProtection, function (req, res) {
23
  res.send('data is being processed')
24
})
25
 
          
26
// views/forms.html
27
  <form action="/process" method="POST">
28
    ...
29
    <input type="hidden" name="_csrf" value="{{ csrftoken }}">
30
    ...
31
  </form>


Sufficient Logging and Monitoring

The exploitation of insufficient logging and monitoring is the basis for every vulnerability. Attackers often poke systems with all the tools available in their arsenal before they find a point of entry. 

Insufficient monitoring often aids attackers because most breaches are only discovered after its occurrence. Most breach studies show it takes over 200 days before a breach is discovered. Many times breaches are not discovered internally, they're discovered by external parties.

Configuring  applications and service to a centralized logging system such as Logz will provide an audit trail for ongoing attacks or attempted data breaches. Monitoring tools such as Prometheus enables engineering teams to collect metrics from target systems as well as trigger alert when pre-defined conditions are met.

Regular pen test exercises are encouraged as it will help harden system logging and monitoring policies as well help teams establish response and recovery plans in the event of a breach.

Conclusion

In the course of this article, we have looked at ten secure practices in Node which aligns with OWASP top 10 web application risks. 

As frameworks and libraries enable engineers and application developers to build complex and robust systems, they also open up those systems to a ton vulnerability. Staying on top of modern security practices will help application developers build better-secured systems for users.

Further Reads

  • Source Code Analysis Tools 
  • OWASP TOP 10: Insecure Deserialization
  • OWASP Top Ten
  • Exploiting Node.js deserialization bug for Remote Code Execution
  • JS-CSP
Database Node.js Vulnerability Data (computing) JavaScript Web application Open source authentication

Opinions expressed by DZone contributors are their own.

Related

  • C# Applications Vulnerability Cheatsheet
  • Nginx + Node.JS: Perform Identification and Authentication
  • What Is SQL Injection and How Can It Be Avoided?
  • Strengthening Web Application Security With Predictive Threat Analysis in Node.js

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!