NodeJS

A1- Injections

SQL Injection

  • Use Parametrized queries or ORM/ODM or a database library.
  • TypeORM:
const repository = connection.getRepository(User);

const user = new User();
user.firstName = "jhon ";
user.lastName = "doe";
user.age = 25;
await repository.save(user);

const allUsers = await repository.find();
const firstUser = await repository.findOne(1); // find by id
const timber = await repository.findOne({ firstName: "jhon", lastName: "doe" });

await repository.remove(timber);
// Create a new user
const jane = await User.create({ firstName: "Jane", lastName: "Doe" });
console.log("Jane's auto-generated ID:", jane.id);
const instance = new MyModel();
instance.my.key = 'hello';
instance.save(function (err) {
  //
});

Regular Expression Injection

-Avoid regex when possible or defer the task to a dedicated library like validator, or safe-regex to check if the regex pattern is safe.

  • Using Safe-regex:
var saferegex = require('safe-regex');
var emailRegex = /^([a-zA-Z0-9])(([\-.]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$/;

// should output false because the emailRegex is vulnerable to redos attacks
console.log(saferegex(emailRegex));
  • Using Validator:
// instead of the regex pattern, use validator:
var validator = require('validator');
console.log(validator.isEmail('[email protected]'));

No sql Injection

  • Perfom Proper input validation and sanitize input before using it for processing by the database query.

  • You can use express-mongo-sanitize to perform input sanitization.

  • To add this as Middleware

const express = require('express');
const bodyParser = require('body-parser');
const mongoSanitize = require('express-mongo-sanitize');

const app = express();

app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

// To remove data, use:
app.use(mongoSanitize());

// Or, to replace prohibited characters with _, use:
app.use(mongoSanitize({
  replaceWith: '_'
}))
  • To Use Module Directly
const mongoSanitize = require('express-mongo-sanitize');

const payload = {...};

// Remove any keys containing prohibited characters
mongoSanitize.sanitize(payload);

// Replace any prohibited characters in keys
mongoSanitize.sanitize(payload, {
  replaceWith: '_'
});

// Check if the payload has keys with prohibited characters
const hasProhibited = mongoSanitize.has(payload);

OS Command Injection

  • Avoid calling OS commands directly
  • Escape values added to OS commands specific to each OS
  • Sanitized untrusted user input (& | ; $ > < ` \ !) properly
  • Parameterization in conjunction with Input Validation
  • Use structured mechanisms that automatically enforce the separation between data and command.
  • Use execFile or spawn instead of exec: When possible, use the child_process module’s execFile or spawn methods instead of exec. Unlike exec, the spawn and execFile method signatures force developers to separate the command and its arguments.

Noncompliant Code

var child_process = require('child_process');

   child_process.exec('ls -l ' + input_path, function (err, data) {
     console.log(data);
   });

Compliant Code

child_process.execFile('/bin/ls', ['-l', input_path], function (err, result) {
     console.log(result)
   });
var ls = child_process.spawn('/bin/ls', ['-l', input_path])  

Server Side JS Injection

  • Validate user inputs on server side before processing.
  • Do not use eval()function to parse user inputs. Avoid using other commands with similar effect, such as setTimeOut(), setInterval(), and Function().
  • For parsing JSON input, instead of using eval(), use a safer alternative such as JSON.parse(). For type conversions use type related parseXXX()methods.
  • Include “use strict” at the beginning of a function, which enables strict mode within the enclosing function scope.

Noncompliant Code

		var preTax = eval(req.body.preTax);
        var afterTax = eval(req.body.afterTax);
        var roth = eval(req.body.roth);

Compliant Code

		var preTax = parseInt(req.body.preTax);
        var afterTax = parseInt(req.body.afterTax);
        var roth = parseInt(req.body.roth);

Log Injection

  • Filter the user input used to prevent injection of Carriage Return (CR) or Line Feed (LF) characters.
  • Limit the size of the user input value used to create the log message.
  • Encode to proper context, or sanitize user input
  • Make sure all XSS defenses are applied when viewing log files in a web browser.

Noncompliant Code

var userName = req.body.userName;
console.log('Error: attempt to login with invalid user: ', userName);	

Compliant Code

var ESAPI = require('node-esapi');
console.log('Error: attempt to login with invalid user: %s', ESAPI.encoder().encodeForHTML(userName));
console.log('Error: attempt to login with invalid user: %s', ESAPI.encoder().encodeForJavaScript(userName));
console.log('Error: attempt to login with invalid user: %s', ESAPI.encoder().encodeForURL(userName));

A2- Broken Authentication & Session Management

Broken Authentication

  • Where possible, implement multi-factor authentication to prevent automated, credential stuffing, brute force, and stolen credential re-use attacks.

  • Ensure registration, credential recovery, and API pathways are hardened against account enumeration attacks by using the same messages for all outcomes.

  • Limit or increasingly delay failed login attempts. Log all failures and alert administrators when credential stuffing, brute force, or other attacks are detected.

  • Minimum passwords length should be at least eight (8) characters long. Combining this length with complexity makes a password difficult to guess and/or brute force.

  • Do not ship or deploy with any default credentials, particularly for admin users.

  • The application may should different HTTP Error code depending on the authentication attempt response.

  • Use a side-channel to communicate the method to reset their password.

  • Ensure that generated tokens or codes in forgot/reset password are:

    • Randomly genererated using a cryptographically safe algorithm.
    • Sufficiently long to protect against brute-force attacks.
    • Stored securely.
    • Single use and expire after an appropriate period.
  • A stronger password can be enforced using the regex below, which requires at least 8 character password with numbers and both lowercase and uppercase letters.

var PASS_RE =/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$/;

Session Management

  • Use a server-side, secure, built-in session manager that generates a new random session ID with high entropy after login.
  • Session IDs should not be exposed in the URL.
  • Session IDs should timeout. User sessions or authentication tokens should get properly invalidated during logout.
  • Passwords, session IDs, and other credentials should not be sent over unencrypted connections.
  • Session IDs should never be under the control of users and clients to create. They should all be generated, controlled, and secured centrally by the authentication and authorisation mechanism.
  • Session IDs should not be stored between browser sessions. They should be destroyed when a browser is closed. This prevents an attacker retrieving a valid session by using the history in a Browser or by finding the Session ID on the client’s local storage.
  • Re-generate a new session id upon login (and best practice is to keep regenerating them upon requests or at least upon sensitive actions like a user’s password reset. Re-generate a session id as follows: By wrapping the below code as a function callback for the method req.session.regenerate()
req.session.regenerate(function() {

  req.session.userId = user._id;

  if (user.isAdmin) {
    return res.redirect("/benefits");
  } else {
    return res.redirect("/dashboard");
  }

})

A3- Sensitive Data Exposure

  • Classify data processed, stored or transmitted by an application. Identify which data is sensitive according to privacy laws, regulatory requirements, or business needs.
  • Apply controls as per the classification.
  • Don’t store sensitive data unnecessarily. Discard it as soon as possible or use PCI DSS compliant tokenization or even truncation. Data that is not retained cannot be stolen.
  • Ensure up-to-date and strong standard algorithms, protocols, and keys are in place; use proper key management.
  • Encrypt all data in transit with secure protocols such as TLS with perfect forward secrecy (PFS) ciphers, cipher prioritization by the server, and secure parameters. Enforce encryption using directives like HTTP Strict Transport Security (HSTS).
  • Disable caching for response that contain sensitive data.
  • Store passwords using strong adaptive and salted hashing functions with a work factor (delay factor), such as Argon2, scrypt, bcrypt or PBKDF2.
  • Verify independently the effectiveness of configuration and settings.
  • Make sure to encrypt all sensitive data at rest.

Compliant Code

// Include crypto module
var crypto = require("crypto");

//Set keys config object
var config = {
    cryptoKey: "a_secure_key_for_crypto_here",
    cryptoAlgo: "aes256", // or other secure encryption algo here
    iv: ""
};

// Helper method create initialization vector
// By default the initialization vector is not secure enough, so we create our own
var createIV = function() {
    // create a random salt for the PBKDF2 function - 16 bytes is the minimum length according to NIST
    var salt = crypto.randomBytes(16);
    return crypto.pbkdf2Sync(config.cryptoKey, salt, 100000, 512, "sha512");
};

// Helper methods to encryt / decrypt
var encrypt = function(toEncrypt) {
    config.iv = createIV();
    var cipher = crypto.createCipheriv(config.cryptoAlgo, config.cryptoKey, config.iv);
    return cipher.update(toEncrypt, "utf8", "hex") + cipher.final("hex");
};

var decrypt = function(toDecrypt) {
    var decipher = crypto.createDecipheriv(config.cryptoAlgo, config.cryptoKey, config.iv);
    return decipher.update(toDecrypt, "hex", "utf8") + decipher.final("utf8");
};

// Encrypt values before saving in database
user.ssn = encrypt(ssn);
user.dob = encrypt(dob);

// Decrypt values to show on view
user.ssn = decrypt(user.ssn);
user.dob = decrypt(user.dob);

A4- XML External Entities (XXE)

  • Disable XML external entity and DTD processing in all XML parsers in the application.
  • Implement positive (“whitelisting”) server-side input validation, filtering, or sanitization to prevent hostile data within XML documents, headers, or nodes.
  • Verify that XML or XSL file upload functionality validates incoming XML using XSD validation or similar.
  • Patch or upgrade all XML processors and libraries in use by the application or on the underlying operating system
  • Whenever possible, use less complex data formats such as JSON, and avoiding serialization of sensitive data.
  • The XML parsing library used is libxmljs which allows for parsing external entities. We can disable parsing of external entities by modifying the flag value noent to false.

Noncompliant Code

module.exports.bulkProducts =  function(req, res) {
    if (req.files.products && req.files.products.mimetype=='text/xml'){
        var products = libxmljs.parseXmlString(req.files.products.data.toString('utf8'), {noent:true,noblanks:true})

Compliant Code

ucts =  function(req, res) {
    if (req.files.products && req.files.products.mimetype=='text/xml'){
        var products = libxmljs.parseXmlString(req.files.products.data.toString('utf8'), {noent:false,noblanks:true})

A5- Broken Access Control

  • Model access controls should enforce record ownership, rather than accepting that the user can create, read, update, or delete any record.
  • Unique application business limit requirements should be enforced by domain models.
  • Disable web server directory listing and ensure file metadata (e.g. .git) and backup files are not present within web roots.
  • Log access control failures, alert admins when appropriate (e.g. repeated failures).
  • Rate limit API and controller access to minimize the harm from automated attack tooling.
  • JWT tokens should be invalidated on the server after logout.
  • When a user changes their role to another one, the administrator must make sure that the earlier access is revoked such that at any given point of time, a user is assigned to only those roles on a need to know basis.
  • Access decisions should be made by checking if the current user has the permission associated with the requested application action.
  • The authentication mechanism should deny all access by default, and provide access to specific roles for every function.
  • In a workflow based application, verify the users’ state before allowing them to access any resources.
  • Developers should use only one user or session for indirect object references.
  • It is also recommended to check the access before using a direct object reference from an untrusted source.

A6- Security Misconfiguration

  • Use latest stable version of node.js and express (or other web framework you are using). Keep a watch on published vulnerabilities of these.
  • Do not run application with root privileges. It may seem necessary to run as root user to access privileged ports such as 80. However, this can achieved either by starting server as root and then downgrading the non-privileged user after listening on port 80 is established, or using a separate proxy, or using port mapping.
  • Review default in HTTP Response headers to prevent internal implementation disclosure.
  • Use generic session cookie names
  • Limit HTTP Request Body size by setting sensible size limits on each content type specific middleware ( urlencoded, json, multipart) instead of using aggregate limitmiddleware. Include only required middleware. For example if application doesn’t need to support file uploads, do not include multipart middleware.
  • If using multipart middleware, have a strategy to clean up temporary files generated by it. These files are not garbage collected by default, and an attacker can fill disk with such temporary files
  • Vet npm packages used by the application
  • Lock versions of all npm packages used, for example using shrinkwarp, to have full control over when to install a new version of the package.
  • Set security specific HTTP headers
  • The default HTTP header x-powered-by can reveal implementation details to an attacker. It can be taken out by including this code in server.js
app.disable("x-powered-by"); 
  • The default session cookie name for express sessions can be changed by setting key attribute while creating express session.
		app.use(express.session({
            secret: config.cookieSecret,
            key: "sessionId",
            cookie: {
                httpOnly: true,
                secure: true
            }
        }));
  • The security related HTTP Headers can be added using helmet middleware as below
// Prevent opening page in frame or iframe to protect from clickjacking
        app.disable("x-powered-by");

        // Prevent opening page in frame or iframe to protect from clickjacking
        app.use(helmet.xframe());

        // Prevents browser from caching and storing page
        app.use(helmet.noCache());

        // Allow loading resources only from white-listed domains
        app.use(helmet.csp());

        // Allow communication only on HTTPS
        app.use(helmet.hsts());

        // Forces browser to only use the Content-Type set in the response header instead of sniffing or guessing it
        app.use(nosniff());

A7- Cross Site Scripting (XSS)

Reflected XSS

var express = require('express');
var app = express();
var xssFilters = require('xss-filters');

app.get('/', function(req, res){
  var firstname = req.query.firstname; //an untrusted input collected from user
  res.send('<h1> Hello, ' + xssFilters.inHTMLData(firstname) + '!</h1>');
});

app.listen(3000);

Stored XSS

var express = require('express');
var app = express();
var xssFilters = require('xss-filters');

app.get('/', function(req, res){
  var firstname = req.query.firstname; //an untrusted input collected from user
  res.send('<h1> Hello, ' + xssFilters.inHTMLData(firstname) + '!</h1>');
});

Dombased XSS

  • Use DOMPurify to sanitize the user inputs :
<script type="text/javascript" src="dist/purify.min.js"></script>
var clean = DOMPurify.sanitize(dirty);

A9- Using Components with known vulnerabilities

  • Do not run application with root privileges
  • Prefer packages that include static code analysis. Check JSHint/JSLint the configuration to know what rules code abide by
  • Prefer packages that contain comprehensive unit tests and review tests for the functions our application uses
  • Review code for any unexpected file or database access
  • Research about how popular the package is, what other packages use it, if any other packages are written by the author, etc
  • Lock version of packages used
  • Watch Github repositories for notifications. This will inform us if any vulnerabilities are discovered in the package in future
  • Remove unused dependencies, unnecessary features, components, files, and documentation.
  • Continuously inventory the versions of both client-side and server-side components (e.g. frameworks, libraries) and their dependencies using tools like versions, DependencyCheck, retire.js, etc. Continuously monitor sources like CVE and NVD for vulnerabilities in the components. Use software composition analysis tools to automate the process. Subscribe to email alerts for security vulnerabilities related to components you use.
  • Only obtain components from official sources over secure links. Prefer signed packages to reduce the chance of including a modified, malicious component.
  • Monitor for libraries and components that are unmaintained or do not create security patches for older versions. If patching is not possible, consider deploying a virtual patch to monitor, detect, or protect against the discovered issue.
  • Every organization must ensure that there is an ongoing plan for monitoring, triaging, and applying updates or configuration changes for the lifetime of the application or portfolio.

A10- Insufficient Logging and Monitoring

  • Ensure all login, access control failures, and server-side input validation failures can be logged with sufficient user context to identify suspicious or malicious accounts, and held for sufficient time to allow delayed forensic analysis.
  • Ensure that logs are generated in a format that can be easily consumed by a centralized log management solutions.
  • Ensure high-value transactions have an audit trail with integrity controls to prevent tampering or deletion, such as append-only database tables or similar.
  • Establish effective monitoring and alerting such that suspicious activities are detected and responded to in a timely fashion.
  • Establish or adopt an incident response and recovery plan, such as NIST 800-61 rev 2 or later. There are commercial and open source application protection frameworks such as OWASP AppSensor, web application firewalls such as ModSecurity with the OWASP ModSecurity Core Rule Set, and log correlation software with custom dashboards and alerting.

Cross Site Request Forgery (CSRF)

  • Use Express csrf middleware. By default this middleware generates a token named “_csrf” which should be added to requests which mutate state (PUT, POST, DELETE), within a hidden form field, or query-string, or header fields.

  • If using method-override middleware then it used before any middleware that needs to know the method of the request, including CSRF middleware.

  • Anti-CSRF tokens are needed for successful form submission, this can be done by using a module like csurf.

  • Check if your framework has built-in CSRF protection and use it. If framework does not have built-in CSRF protection add CSRF tokens to all state changing requests (requests that cause actions on the site) and validate them on backend.

  • Always use SameSite Cookie Attribute for session cookies

  • Use custom request headers

  • Verify the origin with standard headers

  • Use double submit cookies

  • Consider implementing user interaction based protection for highly sensitive operations

  • Remember that any Cross-Site Scripting (XSS) can be used to defeat all CSRF mitigation techniques. See the OWASP XSS Prevention Cheat Sheet for detailed guidance on how to prevent XSS flaws.

  • Do not use GET requests for state changing operations.

  • CSRF can be avoided by creating a unique token in a hidden field which would be sent in the body of the HTTP request rather than in an URL, which is more prone to exposure.

  • Forcing the user to re-authenticate or proving that they are users in order to protect CSRF. For example, CAPTCHA.

  • Simple express example: The following is an example of some server-side code that generates a form that requires a CSRF token to post back.

var cookieParser = require('cookie-parser')
var csrf = require('csurf')
var bodyParser = require('body-parser')
var express = require('express')

// setup route middlewares
var csrfProtection = csrf({ cookie: true })
var parseForm = bodyParser.urlencoded({ extended: false })

// create express app
var app = express()

// parse cookies
// we need this because "cookie" is true in csrfProtection
app.use(cookieParser())

app.get('/form', csrfProtection, function (req, res) {
  // pass the csrfToken to the view
  res.render('send', { csrfToken: req.csrfToken() })
})

app.post('/process', parseForm, csrfProtection, function (req, res) {
  res.send('data is being processed')
})

Inside the view (depending on your template language; handlebars-style is demonstrated here), set the csrfToken value as the value of a hidden input field named _csrf:

<form action="/process" method="POST">
  <input type="hidden" name="_csrf" value="">
  
  Favorite color: <input type="text" name="favoriteColor">
  <button type="submit">Submit</button>
</form>

Compliant Code

		//Enable Express csrf protection
        app.use(express.csrf());

        app.use(function(req, res, next) { 
            res.locals.csrftoken = req.csrfToken(); 
            next(); 
        }); 

Insecure File Upload

  • Whitelist allowed extensions. Only allow safe and critical extensions for business functionality
  • Validate the file type, don’t trust the Content-Type header as it can be spoofed
  • Change the filename to something generated by the application
  • The application should perform filtering and content checking on any files which are uploaded to the server. Files should be thoroughly scanned and validated before being made available to other users. If in doubt, the file should be discarded.
  • It is necessary to have a list of only permitted extensions on the web application. And, file extension can be selected from the list.
  • All the control characters and Unicode ones should be removed from the filenames and their extensions without any exception. Also, the special characters such as “;”, “:”, “>”, “<”, “/” ,”\”, additional “.”, “*”, “%”, “$”, and so on should be discarded as well. If it is applicable and there is no need to have Unicode characters, it is highly recommended to only accept Alpha-Numeric characters and only 1 dot as an input for the file name and the extension; in which the file name and also the extension should not be empty at all (regular expression: [a-zA-Z0-9]{1,200}.[a-zA-Z0-9]{1,10}).
  • Limit the filename length. For instance, the maximum length of the name of a file plus its extension should be less than 255 characters (without any directory) in an NTFS partition.
  • Uploaded directory should not have any “execute” permission and all the script handlers should be removed from these directories.
  • Limit the file size to a maximum value in order to prevent denial of service attacks (on file space or other web application’s functions such as the image resizer).
  • In case of having compressed file extract functions, contents of the compressed file should be checked one by one as a new file.
  • If it is possible, consider saving the files in a database rather than on the filesystem.
  • File uploaders should be only accessible to authenticated and authorised users if possible.
  • Adding the “Content-Disposition: Attachment” and “X-Content-Type-Options: nosniff” headers to the response of static files will secure the website against Flash or PDF-based cross-site content-hijacking attacks.

CORS Misconfiguration

Denial of Service

  • Prevent single point of failure

  • Avoid highly CPU consuming operations

  • Keep Queues short

  • Handle Exceptions

  • Protect overflow and underflow

  • Limit file upload size and extensions

  • Limit total request size to make it harder for resource consuming DoS attack to succeed

  • Prevent input based function and threading interaction

  • Limit the actual payload that user can submit to your app / api / service. You can limit the body payload using body-parser. ExpressJS comes with built-in body-parser that you can use:

const express = require('express');
const app = express();app.use(express.json({ limit: '10kb' })); // Body limit is 10
  • Another useful express feature is express-rate-limit dependency. This dependency lets you set rate limit for users. So basically, you can set maximum amount of requests for each user, after user uses all of his requests, you can lock him out for certain amount of time.
  • Here is how you can configure express-rate-limit: npm install express-rate-limit - Installing it using NPM
const limit = rateLimit({
    max: 100,// max requests
    windowMs: 60 * 60 * 1000, // 1 Hour
    message: 'Too many requests' // message to send
});
app.use('/routeName', limit); // Setting limiter on specific route

Server Side Request Forgery(SSRF)

  • Use a whitelist of allowed domains, resources and protocols from where the web server can fetch resources.
  • Any input accepted from the user should be validated and rejected if it does not match the positive specification expected.
  • If possible, do not accept user input in functions that control where the web server can fetch resources.
  • Disable unused URL schemas.
  • Migrate to IMDSv2 and disable old IMDSv1. IMDSv2 is an additional defence-in-depth mechanism for AWS that mitigates some of the instances of SSRF.

References