9723122, 847 AM Nodejs Security OWASP Cheat Sheet Series
NodeJS Security Cheat Sheet
Introduction
This cheat sheet lists actions developers can take to develop secure Nodes applications. Each
item has a brief explanation and solution that is specific to the Node,js environment.
Context
Node.js applications are increasing in number and they are no different from other frameworks and
programming languages. Node js applications are prone to all kinds of web application
vulnerabilities,
Objective
This cheat sheet aims to provide a list of best practices to follow during development of Node,js
applications.
Recommendations
There are several different recommendations to enhance security of your Node js applications.
These are categorized as:
+ Application Security
+ Error & Exception Handling
+ Server Security
+ Platform Security
Application Security
Use flat Promise chains
‘Asynchronous callback functions are one of the strongest features of Nodes. However, increasing
layers of nesting within callback functions can become a problem. Any multistage process can
‘ntps:ifeheatshectseries.owasp.orgicheatsheetsiNodejs_Securty_Cheat_Sheethtml ane9723122, 847 AM Nodejs Security OWASP Cheat Sheet Series
becomenested 10 or mote levels deep. This problem is referred to.as a "Pyramid of Doom' or
"Callback Hell’. In such code, the ertors and results get lost within the callback. Promises are a
good way to write asynchronous code without getting into nested pyramids. Promises provide top-
down execution while being asynchronous by deliveringerrors and results tonext .then function.
‘Another advantage of Promisesis the way Promises handieemrors. fan er occursin a Promise
class, it skips over the then functions andinvokes the first .cateh function it finds. This way
Promises provide a higher assurance of capturing and handling errors. As.a principle, you can
makeall your asynchronous code (apart from emitters) retum promises. it should be neted that
Promise calls can also become a pyramid. In order to completely stay away ftom "Callback Hell,
flat Promise chains should be used. f the module you are using does net support Promises, you
‘can convert base object 1o a Promise by using Promise.promisifyAlL() function.
The following code snippet is an exemple of "Callback Hell
function func (name, callback) {
// operations that takes a bit of time and then calls the callback
i
function func2(name, callback) {
// operations that takes a bit of time and then calls the callback
y
function func3(name, callback) {
// operations that takes a bit of time and then calls the callback
+
function fune4(name, callback) {
// operations that takes a bit of time and then calls the callback
+
funci("inputi", function(err, result1){
af(err){
// error operations
d
else {
J /some operations
fune2("“input2", function(err, result2){
af(err){
/error operations
)
else{
//sone operations
funea("input3”, function(err, result2){
af(err){
Jlerror operations
+
else
1/ sone operations
funea("input 4”, function(err, resulta) {
ater)
// error operations
‘ntps:ifeheatshectseries.owasp.orgicheatsheetsiNodejs_Securty_Cheat_Sheethtml 2189123/22, 8:47 AM Nodejs Security OWASP Cheat Sheet Series
+
else {
1 some operations
+
De
+
Di
,
vi
,
YD:
‘The above code can be securely written as follows using a flat Promise chain:
function funct(name) {
// operations that takes a bit of time and then resolves the promise
}
function fune2(name) {
// operations that takes a bit of time and then resolves the promise
}
function fune3(name) {
// operations that takes a bit of time and then resolves the promise
}
funetion fune4(name) {
// operations that takes a bit of time and then resolves the promise
+
funet ("input")
then (function (result)
return func2("input2") ;
»
then (function (result){
return func3("input3") ;
»
then (function (result){
return func4("input4");
»
seatch(function (error) {
11 error operations
DD:
Andusing async/await:
function asyne funct(name) {
// operations that takes a bit of time and then resolves the promise
}
function async func2(name) {
// operations that takes a bit of time and then resolves the promise
}
function async fune3(nane) {
// operations that takes a bit of time and then resolves the promise
‘ntps:ifeheatshectseries.owasp.orgicheatsheetsiNodejs_Securty_Cheat_Sheethtml ae9723122, 847 AM Nodejs Security OWASP Cheat Sheet Series
3
function async fune4(nane) {
1/ operations that takes a bit of time and then resolves the promise
}
(async() => {
try {
et rest
det res2
et res
let resd = await funed("input2");
} catch(err) {
// etror operations
)
vO;
Set request size limits
Buffering and parsing of request bodies can be aresource intensive task. If there isno limit on the
size of requests, attackers can send requests with large request bodies that can exhaust server
memory and/or fill disk space. You can limit the request body size for all requests using raw-body.
const contentType = require('content-type’)
const express = require(' express’ )
const getRawBody = require( raw-body’)
const app = express()
app.use(function (req, res, next) {
if (I['POST’, "PUT’, ‘DELETE'].includes(req.method)) {
next ()
return
)
getRawBody(req, {
Length: req.headers[ ‘content-length" ],
Limit: °7kb',
encoding: contentType.parse(req).paraneters.charset
}, function (err, string) {
if (err) return next(err)
req.text = string
next ()
»
»v
However, fixing a request size mt forall requests may net be the correct behavior, since some
requests may have a large payload in the request body, such as when uploading a file. Also, input
with a JSON type is more dangerous than a multipart input, since parsing JSON isa blocking
‘ntps:ifeheatshectseries.owasp.orgicheatsheetsiNodejs_Securty_Cheat_Sheethtml ane9723122, 847 AM Nodejs Security OWASP Cheat Sheet Series
operation. Therefore, you should set request size limits for diferent content types. You can
‘accomplish this very easily with express middleware as follows:
app.use(express.urlencoded({ extended: true, limit: "1kb” }));
app.use(express. json({ limit: "1kb” }));
It should be noted that attackers can change the Content-Type header of the request and bypass
request size limits. Therefore, before processing the request, data contained in the request should
be validated against the content type stated in the request headers. If content type validation for
each request affects the performance severely, you can only validate specific content types or
request larger than a predetermined size.
Donot block the event loop
Node,js is very different from common application platforms that use threads. Node,js has a
single-thread event-driven architecture. By means of this architecture, throughput becomes high
and the programming model becomes simpler. Nodejs is implemented around a non-blocking 1/0
event loop. With this event loop, there is no waiting on I/O or context switching. The event loop
looks for events and dispatches them to handler functions. Because of this, when CPU intensive
JavaScript operations are executed, the event loop waits for them to finish. This is why such
operations are called "blocking’. To overcome this problem, Nodes allows assigning callbacks to
lO-blocked events. This way, the main application is not blocked and callbacks run asynchronously.
Therefore, as a general principle, all blocking operations should be done asynchronously so that
the event loop is not blocked.
Even if you perform blocking operations asynchronously, your application may still not serve as
expected. This happens if there is a code outside the callback that relies on the code within the
callback to run first. For example, consider the following code:
const fs = require('fs")
fs.readFile('/file.txt', (err, data) => {
// perform actions on’ file content
Dv:
Fs.unlinkSyne(' /file. txt’);
In the above example, unlinkSync function may run before the callback, which will delete the file
before the desired actions on the file content is done. Such race conditions can also affect the
security of your application. An example would be a scenario where authentication is performed in
a callback and authenticated actions are run synchronously. In order to eliminate such race
conditions, you can write all operations that rely on each other in a single non-blocking function. By
doing so, you can guarantee that all operations are executed in the correct order, For example,
above code example can be written in anon-blocking way as follows:
‘ntps:ifeheatshectseries.owasp.orgicheatsheetsiNodejs_Securty_Cheat_Sheethtml site9723122, 847 AM Nodejs Security OWASP Cheat Sheet Series
const fs = require(‘fs");
fs.readFile('/file.txt', (err, dete) => {
J/ perform actions on file content
fs.unlink('/file.txt', (err) => {
if (err) throw err
»;
Di
In the above code, call to unlink the file and other file operations are within the same callback. This,
provides the correct order of operations.
Perform input validation
Input validation is a crucial part of application security. Input validation failures can result in many
different types of application attacks. These include SQL Injection, Cross-Site Scripting, Command
Injection, Local/Remote File Inclusion, Denial of Service, Directory Traversal, LDAP Injection and
many other injection attacks. In order to avoid these attacks, input to your application should be
sanitized first. The best input validation technique is to use a list of accepted inputs. However, if
this is not possible, input should be first checked against expected input scheme and dangerous
inputs should be escaped. In order to ease input validation in Node.js applications, there are some
modules like validator and mongo-express-sanitize, For detailedinformation on input validation,
please refer to Input Validation Cheat Sheet,
JavaScript is a dynamic language and depending on how the framework parses @ URL, the data
seen by the application code can take on many different forms. Here are some examples after
Parsing aquery string in express. js.
URL Content of request.quety-foo in code
2foorbar bar’ (sting)
TfoSabBFETGORUBZ ['bar’, ‘baz'] (aney of string)
rool l=bar (bar } (aray of string)
?f00[ )=bar&foo| }=baz ['bar', "baz') (amay of string)
2foo|ber|=baz {bar : ‘baz’ } (cbjectwitha key)
2?fool I=bar bar’ ] (aay of string)
‘ntps:ifeheatshectseries.owasp.orgicheatsheetsiNodejs_Securty_Cheat_Sheethtml es9723122, 847 AM Nodejs Security OWASP Cheat Sheet Series
URL Content of request.quety.foo in code
?Fool Ibazsbar ['bar'] (aray of string- postfix is lost)
?Fool } [baz] =bar [ (baz: ‘bar’ } | (array of object)
2feolbar }[baz|=bar { foo: ( bar: { baz: ‘bar’ } } } (cbect tree)
2f00|10]=bar&fool9|=baz ‘baz’, ‘ber’ | (aray of string-notice ordet)
Pool tostring]=bar 4} (cbject where calling tostring() willfail)
Perform output escaping
In addition to input validation, you should escape all HTML and JavaScript content shown to users
via application in order to prevent cross-site scripting (XSS) attacks. Youcan use escape-him! or
node-esapi libraries to perform output escaping.
Perform application activity fogging
Logging application activity is an encouraged good practice. It makes it easier to debug any errors,
encountered during application runtime. ttis also useful for security concems, since it can be used
duringincident response. In addition, these logs can be used to feed Intrusion
Detection/Prevention Systems (IDS/IPS). In Nodes, there ate modules such as Winston, Bunyan,
(oF Pino to perform application activity logging. These modules enable streaming and querying logs,
and they provide a way to handle uncaught exceptions.
With the following code. you can log application activities in both console and a desired log file
const logger = new (Winston.Logger) ({
transports: [
ew (winston.transports.Console)(),
ew (winston.transports.File)({ filename: ‘application.log' })
level: ‘verbose’
You can provide different transports so that you can save errors to‘ separate log file and general
application logs to a different log file. Additional information on security logging can be found in
Logging Cheat Sheet,
Monitor the eventt oop
‘ntps:ifeheatshectseries.owasp.orgicheatsheetsiNodejs_Securty_Cheat_Sheethtml m8.9723122, 847 AM Nodejs Security OWASP Cheat Sheet Series
\When your application server is under heavy network traffic, it maynat be able to serveits users.
This is essentially a type of Denial of Service (DoS) attack. The toobusy-js module allows you to
monitor the event loop. It keeps track of the response time, and when it goes beyond a certain
threshold. this module can indicate your server is too busy. In that case, you can stop processing
incoming requests and send them 503 Server Too Busy message so that your application stay
responsive. Example use of the toobusy-js module is shown here:
const toobusy = require(' toobusy-js');
const express = require( express’);
const app = express();
‘app-use(function(req, res, next) {
if (toobusy()) {
// Jog if you see necessary
res.send(583, “Server Too Busy");
) else {
next ();
,
Dd:
‘Take precautions against brute-forcing
Brute forcing is @ common threat to all web applications. Attackers can use brute-forcing as a
password guessing attack to obtain account passwords. Therefore, application developers should
take precautions against brute-force attacks especially in login pages. Node js has several
modules available for this purpose. Express-bouncer, express-brute and rate-limiter are just some
examples. Based on yourneeds and requirements, you should choose one or more of these
modules and use accordingly. Express-bouncer and express-brute madules work very similar and
they both increase the delay with each failed request. They can beth be arranged for a specific
route. These modules can be used as follows:
const bouncer = require( 'express-bouncer" );
bouncer .whitelist.push('127.0.0.1'); // allow an IP address
// give 2 custom error message
bouncer blocked = function (req, res, next, remaining) {
res.send(429, “Too many requests have been made. Please wait
renaining/1@60 +" seconds.");
k
1/ route to protect
‘app.post(*/login®, bouncer.block, function(req, res) {
af (LoginFailed){ }
else {
bouncer.reset( req );
}
Di
‘ntps:ifeheatshectseries.owasp.orgicheatsheetsiNodejs_Securty_Cheat_Sheethtml ane9723122, 847 AM Nodejs Security OWASP Cheat Sheet Series
const ExpressBrute = require(‘express-brute’);
const store = new FxpressBrute.Menorystore(); // stores state locally, don't use
this in production
const bruteforce = new ExpressBrute(store) ;
‘app. post(" /auth’,
bruteforce-prevent, // error 429 if we hit this route too often
function (req, res, next) {
res.send('Success!');
»
i
Apart from express-bouncer and express-brute, the rate-limiter module can also help to prevent
brute-forcing attacks. it enables specifying how many requests a specific IP address can make
duringa specified time petiod.
const limiter = new RateLimiter();
Limiter -addLimit(’/login’, ‘GET’, 5, 588); // login page can be requested 5 times
at max within 502 seconds
CAPTCHA usage is also ancther common mechanism used against brute-forcing. There are
modules developed for Node.js CAPTCHAs. A common module usedin Node js applications is
svg-captcha. It can beused as follows:
const svgCaptcha = require(’svg-captcha’
‘app.get('/captcha', function (req, res) {|
const captcha = sveCaptcha.create( )
req.session.captcha = captcha. text ;
res.type('svg');
res.status(200). send(captcha.data) ;
yD:
‘Account lockout isa recommended solution to keep attackers away from your valid users. Account
lockout is possible with many modules like mongoose. You can refer to this blog post to see how
account lockout is implemented in mongoose.
Use Anti-CSRF tokens
Cross-Site Request Forgery (CSRF) aims to perform authorized actions on behalf of an
‘authenticated user, while the user is unaware of this action. CSRF attacks are generally performed
for state-changing requests like changing a password, adding users or placing orders. Csurf is an
express middleware that can be used to mitigate CSRF attacks. It can beused as follows:
const esrf = require('csurf
esrfProtection = esrf({ cookie: true });
‘ntps:ifeheatshectseries.owasp.orgicheatsheetsiNodejs_Securty_Cheat_Sheethtml ons9723122, 847 AM Nodejs Security OWASP Cheat Sheet Series
app.get('/form', csrfProtection, function(reg, res) {
res.render('send’, { csrfToken: req.csrfToken() })
»v
app.post("/process’, parseForm, csrfProtection, function(req, res) {
res.send(‘data is being processed’ )
Di
After writing this code, you also need to add esrfroken to yourHTMIL form, which can be easily
doneas follows:
cosrf” value="*>
fidden® name
", "")); // custom configuration
‘ntps:ifeheatshectseries.owasp.orgicheatsheetsiNodejs_Securty_Cheat_Sheethtml 13889723122, 847 AM Nodejs Security OWASP Cheat Sheet Series
« X-Frame-Options: determines if a page can be loaded via a of an