Categories
Security

More Security Best Practices for Using Express Apps in Production

When we use an Express app for production usage, i.e. it’s used by external users, we have to be careful about security since it’s available to the outside world.

In this article, we’ll look at some security best practices when running Express apps in a production environment.

Ensure Dependencies are Secure

We can check if the Node packages we use in our apps are secure by using NPM’s automatic security check when we install packages.

We can also run manual security checks with npm audit.

Both commands check the Node packages we use for security vulnerabilities.

To stay even more secure, we can use Snyk. It offers a command-line tool and Github integration to check our repositories automatically against a list of security vulnerabilities that Snyk has.

It’s available as a Node package. To install it, we run npm i -g snyk.

We can use snyk test to test our apps for vulnerabilities.

snyk wizard is a command-line wizard that walks us through the process of applying updates to fix vulnerabilities that were found.

Avoiding Other Known Vulnerabilities

We can check the Node Security Project website or Snyk advisories that may affect Express and other Node modules that we use in our app.

These databases are great resources for learning about Node security.

We also have to be aware of general web vulnerabilities that are in the wild on the web.

Cross-Site Request Forgery

Cross-site request forgery (CSRF) is a vulnerability where an attack can make unauthorized requests to our app as the user that our app trusts.

Attackers can do this in many ways like specially crafted commands, image tags, hidden forms, JavaScript XMLHttpRequests, etc.

We can use the csurf middleware to protect our app from CSRF attacks. It adds a req.csrfToken() function so that we can check against the CSRF token when we handle requests to make sure that the request is actually coming from users we trust.

Filter and Sanitize Inputs

We should filter and sanitize inputs so that attackers can’t run code in our system.

Sanitizing inputs help with this since strings with special characters may be directly injected into code in some places.

To prevent input strings from having special characters that are part of some malicious code snippet, we should escape those characters so they’re turned in more harmless characters.

SQL Injection

Preventing SQL injection attacks is another reason to sanitize inputs. Not only that we should sanitize inputs, but we should also make sure that input strings are never directly injected into SQL statements.

If we let strings be interpolated straight into SQL commands, then attackers can run anything they wish.

We definitely don’t want that to happen because they may run commands to steal, data, corrupt data, destroy our database and more.

To prevent SQL injection, we should use parameterized queries and prepared statements.

Prepared statements will sanitize inputs before running them so that we don’t have to interpolate values into SQL command strings directly.

For example, if we use SQLite as our database in our Express app, we can write something like:

app.post('/', (req, res) => {  
    const { name, age } = req.body;  
    db.serialize(() => {  
        const stmt = db.prepare('INSERT INTO persons (name, age) VALUES (?, ?)');  
        stmt.run(name, age);  
        stmt.finalize();  
        res.json(req.body);  
    })  
})

In this case, name and age are sanitized before the prepared statement:

INSERT INTO persons (name, age) VALUES (?, ?)

is run.

name and age are sanitized and replace the question marks when we run:

stmt.run(name, age);

db.prepare is a method in the SQLite3 package. Other database libraries should also support prepared statements.

We can use the sqlmap tool to detect SQL injection vulnerabilities in our app.

Testing TLS Security

We should use the nmap and sslyze tools to test the configuration of our SSL /TLS ciphers, keys, renegotiation, and validation of our certificates.

nmap is a utility for network discovery and security audits. It does a variety of scans to check for vulnerabilities in our systems.

sslyze is used specifically for SSL/TLS scanning to check for configuration issues with our SSL/TLS setup.

Check for Regular Expression Denial of Service Attacks

Regular expression denial of service attacks (ReDoS) is an attack that exploits the fact that most regex implementations may reach extreme situations to make them work extremely slowly.

Attackers can send inputs that make our app that enter these extreme situations and make our app hang.

Grouping with repetition of various kinds are examples of regexes that may be subject to this kind of vulnerability.

We can use safe-regex to check that our regexes aren’t susceptible to (ReDos) attacks.

Conclusion

When we put our Express app in a production environment, there’s more to think about than just getting our app to work.

We have to be aware of SQL injection attacks and other code injection attacks so that our apps won’t run malicious code. To do this, we sanitize inputs and run SQL queries as prepared statements.

Also, we have to be aware of SSL/TLS security and check if our server configuration is secure by using various utilities to check our server configuration.

Our app’s dependencies should be checked for vulnerabilities so that we can update them to avoid the security issues.

Finally, we should make sure the regexes that we use aren’t vulnerable to (ReDoS) attacks, which may bring down our app.

Categories
Security

Security Best Practices for Using Express Apps in Production

When we use an Express app for production, i.e. it’s used by external users, we have to be careful about security since it’s available to the outside world.

In this article, we’ll look at some security best practices when running Express apps in a production environment.

Don’t Use Outdated Versions of Express

Outdated versions of Express are no longer maintained and may have unpatched security vulnerabilities, leaving our apps at risk for all sorts of attacks.

Use TLS

If we’re transporting data that needs to be secured, we should use TLS so that users’ data is protected. This means that we avoid man-in-the-middle attacks and packet sniffing.

Even our apps don’t transport secure data, it still gives users more confidence that our website is secure.

We can get free TLS certificates with Let’s Encrypt. It’s an automated service that generates new certificates that are trusted by browsers,

Use Helmet

Helmet is a series of Express middlewares to protect our apps with some well-known security vulnerabilities on the web by setting HTTP headers.

The middlewares included with Helment includes:

  • csp — sets the Content-Security-Policy header to help prevent cross-site scripting attacks and other cross-site injections.
  • hidePoweredBy — removes the X-Powered-By header
  • hpkp — adds public key pinning header to prevent man-in-the-middle attacks with forged certificates
  • hsts — sets Strict-Transport-Security header that enforces HTTPS connects to the server.
  • ieNoOpen 0 sets X-Download-Options for IE 8 or later
  • noCache — sets Cache-Control and Pragma header to disable client-side caching
  • noSniff — sets X-Content-Type-Options header to prevent browsers from MIME-sniffing a response away from the declared content type
  • frameguard — sets the X-Frame-Options header to provide clickjacking protection
  • xssFilter — sets X-XSS-Protection to enable cross-site scripting filter in most recent web browsers

It’s a good idea to at least disable the x-powered-by response header so that attackers won’t know that our app is an Express app and launch specific attacks for it.

Use Cookies Securely

We can use the express-session or cookie-session middleware to use cookies securely.

express-session stores session data on the server. It only saves the session ID on the cookie itself.

We can set up a session store for production use since it uses an in-memory store by default.

cookie-session stores the whole cookie on the client-side. We shouldn’t exceed 4093 bytes when we set our cookies.

The cookie data will be visible to the client, so secret data shouldn’t be sent to the client with cookie-session .

Don’t Use Default Session Cookie Name

The default session cookie name set by Express shouldn’t be used since it can identify that an app is running on Express.

We can do this by passing in an object to session as follows:

const express = require('express');
const bodyParser = require('body-parser');
const session = require('express-session');
const app = express();

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

app.set('trust proxy', 1);
app.use(session({
  secret: 'secret',
  name: 'sessionId'
}))

app.listen(3000);

Set Cookie Security Options

To enhance security, we can set various cookie options. The following options are available:

  • secure — ensures browsers only send cookie over HTTPS
  • httpOnly — ensures cookie is sent only over HTTP(S) and not client-side JavaScript. This helps us prevent XSS attacks
  • domain — indicates the domain of the cookie, which can be used to compare against the domain of the server in the URL that’s requesting the cookie.
  • path — indicates the path to compare against the requested path, then send the cookie in the request
  • expires — set the expiration date for persistent cookies.

For example, we can use it as follows:

const express = require('express');
const bodyParser = require('body-parser');
const session = require('express-session');
const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const expiryDate = new Date(Date.now() + 60 * 60 * 1000)
app.use(session({
  name: 'session',
  secret: 'secret',
  keys: ['foo', 'bar'],
  cookie: {
    secure: true,
    httpOnly: true,
    domain: 'EnormousBeneficialScript--five-nine.repl.co',
    path: '/',
    expires: expiryDate,
  }
}));

app.get('/', (req, res) => {
  req.session.foo = 'foo';
  res.send(req.session.foo);
})

app.listen(3000);

Prevent Brute-Force Attacks

We can rate limit our authorization routes so that attackers can’t use brute-force attacks to guess the credentials stored in our app’s database.

For example, we can limit by IP address by limiting the number of failed attempts that people can make if they have the same user name and IP address.

Then we lock out the user for some time if the number of failed attempts what we can tolerate.

If more than 100 failed attempts in a day are made then we block the IP address.

We can use the rate-limiter-flexible package to do rate-limiting of our routes.

Conclusion

To keep our Express app secure in a production environment, we should take a few precautions.

First, we should use TLS for transporting data to keep them from being sniffed and prevent man-in-the-middle attacks.

Next, we should set headers in ways that prevent users from finding information about our app and to enforce secure communication.

Also, we should use cookies securely by signing it with a secret and set the security options for them.

Finally, to prevent brute-force attacks, we should set a rate limit for our API calls so that attackers can’t guess our users’ credentials by repeated login attempts.