0% found this document useful (0 votes)
43 views

Nodejs Security Cheatsheet

Nodejs Security Cheatsheet

Uploaded by

Rizki Kurniawan
Copyright
© © All Rights Reserved
Available Formats
Download as PDF or read online on Scribd
0% found this document useful (0 votes)
43 views

Nodejs Security Cheatsheet

Nodejs Security Cheatsheet

Uploaded by

Rizki Kurniawan
Copyright
© © All Rights Reserved
Available Formats
Download as PDF or read online on Scribd
You are on page 1/ 18
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 ane 9723122, 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 218 9123/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 ae 9723122, 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 ane 9723122, 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 site 9723122, 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 es 9723122, 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 ane 9723122, 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 ons 9723122, 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 1388 9723122, 847 AM Nodejs Security OWASP Cheat Sheet Series « X-Frame-Options: determines if a page can be loaded via a of an