Conseils de sécurité Express.js: comment enregistrer et sécuriser votre application

Prenez 7 mesures pour vous assurer que votre application est invincible

Votre téléphone est-il verrouillé? Avez-vous un code PIN, un mot de passe, une empreinte digitale ou un FaceID? Je suis sûr à 99% que vous le faites. Et il est clair pourquoi - vous vous souciez de votre sécurité. De nos jours, protéger votre téléphone est aussi important que de se brosser les dents le matin.

Pour les développeurs de logiciels diligents et attentifs, il est tout aussi important de protéger leur application pour protéger leurs téléphones. Si vous êtes un développeur et que vous choisissez de le négliger, veuillez reconsidérer votre approche. Si vous êtes propriétaire de projet et que votre équipe de développement dit que la sécurité des données peut attendre, veuillez reconsidérer votre équipe.

Dans cet article, je souhaite expliquer comment m'assurer que votre projet Express.js est sûr et invincible aux attaques malveillantes.

Il existe 7 mesures simples et pas très simples à prendre pour la sécurité des données:

  1. Utilisez des versions fiables d'Express.js
  2. Sécurisez la connexion et les données
  3. Protégez vos cookies
  4. Sécurisez vos dépendances
  5. Validez la saisie de vos utilisateurs
  6. Protégez votre système contre la force brute
  7. Contrôlez l'accès des utilisateurs

Examinons chacun de plus près.

1. Utilisez des versions fiables d'Express.js

Les versions obsolètes ou obsolètes d'Express.js sont interdites. Les 2e et 3e versions d'Express ne sont plus prises en charge. Dans ceux-ci, les problèmes de sécurité ou de performance ne sont plus résolus.

En tant que développeur, vous devez absolument migrer vers Express 4. Cette version est une révolution! C'est assez différent en termes de système de routage, de middleware et d'autres aspects mineurs.

2. Sécurisez la connexion et les données

Pour sécuriser les en-têtes HTTP, vous pouvez utiliser Helmet.js - un module Node.js utile. Il s'agit d'une collection de 13 fonctions middleware permettant de définir les en-têtes de réponse HTTP. En particulier, il existe des fonctions pour définir la politique de sécurité du contenu, gérer la transparence des certificats, empêcher le détournement de clic, désactiver la mise en cache côté client ou ajouter de petites protections XSS.

npm install helmet --save

Même si vous ne souhaitez pas utiliser toutes les fonctions de Helmet, le minimum absolu à faire est de désactiver l'en-tête X-Powered-By:

app.disable('x-powered-by')

Cet en-tête peut être utilisé pour détecter que l'application est alimentée par Express, ce qui permet aux pirates de mener une attaque précise. L'en-tête X-Powered-By n'est certainement pas le seul moyen d'identifier une application exécutée Express, mais c'est probablement le plus courant et le plus simple.

Pour protéger votre système contre les attaques de pollution des paramètres HTTP, vous pouvez utiliser HPP. Ce middleware met de côté des paramètres tels que req.query et req.body et sélectionne la dernière valeur de paramètre à la place. La commande d'installation se présente comme suit:

npm install hpp --save 

Pour crypter les données envoyées du client au serveur, utilisez Transport Layer Security (TLS). TLS est un protocole cryptographique de sécurisation du réseau informatique, le descendant du cryptage Secure Socket Layer (SSL). TLS peut être géré avec Nginx - un serveur HTTP gratuit mais efficace - et Let's Encrypt - un certificat TLS gratuit.

3. Protégez vos cookies

Dans Express.js 4, il existe deux modules de session de cookies:

  • express-session (dans Express.js 3, c'était express.session)
  • cookie-session (dans Express.js 3, c'était express.cookieSession)

Le module de session express stocke l'ID de session dans le cookie et les données de session sur le serveur. La session de cookie stocke toutes les données de session dans le cookie.

En général, la session cookie est plus efficace. Cependant, si les données de session que vous devez stocker sont complexes et susceptibles de dépasser 4096 octets par cookie, utilisez express-session. Une autre raison d'utiliser la session express est lorsque vous devez garder les données du cookie invisibles pour le client.

En outre, vous devez définir les options de sécurité des cookies, à savoir:

  • sécurise
  • httpSeulement
  • domaine
  • chemin
  • expire

Si «sécurisé» est défini sur «vrai», le navigateur enverra des cookies uniquement via HTTPS. Si «httpOnly» est défini sur «true», le cookie ne sera pas envoyé via le client JS mais via HTTP (S). La valeur de «domaine» indique le domaine du cookie. Si le domaine du cookie correspond au domaine du serveur, «chemin» est utilisé pour indiquer le chemin du cookie. Si le chemin du cookie correspond au chemin de la demande, le cookie sera envoyé dans la demande. Enfin, comme son nom l'indique, la valeur «expire» correspond à l'heure à laquelle les cookies expireront.

Une autre recommandation importante est de ne pas utiliser le nom de cookie de session par défaut. Cela peut permettre aux pirates de détecter le serveur et de lancer une attaque ciblée. Utilisez plutôt des noms de cookies génériques.

4. Sécurisez vos dépendances

Nul doute que npm est un puissant outil de développement Web. Cependant, pour garantir le plus haut niveau de sécurité, envisagez de n'utiliser que la 6e version de celui-ci - npm @ 6. Les plus anciens peuvent contenir de graves vulnérabilités en matière de sécurité des dépendances, qui mettront en danger l'ensemble de votre application. De plus, pour analyser l'arborescence des dépendances, utilisez la commande suivante:

npm audit

npm audit peut vous aider à résoudre de vrais problèmes dans votre projet. Il vérifie toutes vos dépendances dans les dépendances, devDependencies, bundledDependencies et optionalDependencies, mais pas vos peerDependencies. Ici, vous pouvez lire toutes les vulnérabilités actuelles dans tous les packages npm.

Securing dependencies

Un autre outil pour assurer la sécurité des dépendances est Snyk. Snyk exécute la vérification de l'application pour identifier si elle contient une vulnérabilité répertoriée dans la base de données open source de Snyk. Pour effectuer la vérification, exécutez trois étapes simples.

Étape 1. Installez Snyk

npm install -g snyk cd your-app

Étape 2. Exécutez un test

snyk test

Step 3. Learn how to fix the issue

snyk wizard

Wizard is a Snyk method, which explains the nature of the dependency vulnerability and offers ways of fixing it.

5. Validate the input of your users

Controlling user input is an extremely important part for server-side development. This is a no less important problem than unauthorized requests, which will be described in the seventh part of this article.

First of all, wrong user input can break your server when some values are undefined and you do not have error handling for a specific endpoint. However, different ORM systems can have unpredictable behavior when you try to set undefined, null, or other data types in the database.

For example, destroyAll method in Loopback.js ORM (Node.js framework) can destroy all data in a table of the database: when it does not match any records it deletes everything as described here. Imagine that you can lose all data in a production table just because you have ignored input validation.

Use body/object validation for intermediate inspections

To start with, you can use body/object validation for intermediate inspections. For example, we use ajv validator which is the fastest JSON Schema validator for Node.js.

const Ajv = require('ajv'); const ajv = new Ajv({allErrors: true}); const speaker = { 'type': 'object', 'required': [ 'id', 'name' ], 'properties': { 'id': { 'type': 'integer', }, 'name': { 'type': 'string', }, }, }; const conversation = { type: 'object', required: [ 'duration', 'monologues' ], properties: { duration: { type: 'integer', }, monologues: { type: 'array', items: monolog, }, }, }; const body = { type: 'object', required: [ 'speakers', 'conversations' ], properties: { speakers: { type: 'array', items: speaker, }, conversations: { type: 'array', items: conversation, }, }, }; const validate = ajv.compile(body); const isValidTranscriptBody = transcriptBody => { const isValid = validate(transcriptBody); if (!isValid) { console.error(validate.errors); } return isValid; };

Handle errors

Now, imagine that you forgot to check a certain object and you do some operations with the undefined property. Or you use a certain library and you get an error. It can break your instance, and the server will crash. Then, the attacker can ping a specific endpoint where there is this vulnerability and can stop your server for a long time.

The simplest way to do an error handling is to use try-catch construction:

try { const data = body; if (data.length === 0) throw new Error('Client Error'); const beacons = await this.beaconLogService.filterBeacon(data); if (beacons.length > 0) { const max = beacons.reduce((prev, current) => (prev.rssi > current.rssi) ? prev : current); await this.beaconLogService.save({ ...max, userId: headers['x-uuid'] }); return { data: { status: 'Saved', position: max }, }; } return { data: { status: 'Not valid object, }, }; } catch(err) { this.logger.error(err.message, err.stack); throw new HttpException('Server Error', HttpStatus.INTERNAL_SERVER_ERROR); }

Feel free to use a new Error(‘message’) constructor for error handling or even extend this class for your own purpose!

Use JOI

The main lesson here is that you should always validate user input so you don't fall victim to man-in-the-middle attacks. Another way to do it is with the help of @hapi/joi – a part of the hapi ecosystem and a powerful JS data validation library.

Pay attention here that the module joi has been deprecated. For this reason, the following command is a no go:

npm install joi

Instead, use this one:

npm install @hapi/joi

Use express-validator

One more way to validate user input is to use express-validator – a set of express.js middlewares, which comprises validator.js and function sanitizer. To install it, run the following command:

npm install --save express-validator 

Sanitize user input

Also, an important measure to take is to sanitize user input to protect the system from a MongoDB operator injection. For this, you should install and use express-mongo-sanitize:

npm install express-mongo-sanitize

Protect your app against CSRF

Besides, you should protect your app against cross-site request forgery (CSRF). CSRF is when unauthorized commands are sent from a trusted user. You can do this with the help of csurf. Prior to that, you need to make sure that session middleware for cookies is configured as described earlier in this article. To install this Node.js module, run the command:

npm install csurf 

6. Protect your system against brute force

A brute force attack is the simplest and most common way to get access to a website or a server. The hacker (in most cases automatically, rarely manually) tries various usernames and passwords repeatedly to break into the system.

These attacks can be prevented with the help of rate-limiter-flexible package. This package is fast, flexible, and suitable for any Node framework.

To install, run the following command:

npm i --save rate-limiter-flexible yarn add rate-limiter-flexible

This method has a simpler but more primitive alternative: express-rate-limit. The only thing it does is limiting repeated requests to public APIs or to password reset.

npm install --save express-rate-limit

7. Control user access

Among the authentication methods, there are tokens, Auth0, and JTW. Let’s focus on the third one! JTW (JSON Web Tokens) are used to transfer authentication data in client-server applications. Tokens are created by the server, signed with a secret key, and transferred to a client. Then, the client uses these tokens to confirm identity.

Express-jwt-permissions is a tool used together with express-jwt to check permissions of a certain token. These permissions are an array of strings inside the token:

"permissions": [   "status",   "user:read",   "user:write" ]

To install the tool, run the following command:

npm install express-jwt-permissions --save

To Wrap Up

Here, I have listed the essential Express.js security best practices and some tools that can be used along the way.

Just to review:

Securing dependencies

I strongly recommend that you make sure that your application is resistant to malicious attacks. Otherwise, your business and your users may suffer significant losses.

Do you have an idea for Express.js project?

My company KeenEthics is experienced in express js development. In case you need a free estimate of a similar project, feel free to get in touch.

If you have enjoyed the article, you should definitely continue with a piece on data safety in outsourcing to Ukraine: KeenEthics Team on Guard: Your Data is Safe in Ukraine. The original article posted on KeenEthics blog can be found here: express js security tips.

P.S.

A huge shout-out to Volodia Andrushchak, Full-Stack Software Developer @KeenEthics for helping me with the article.

The original article posted on KeenEthics blog can be found here: Express.js Security Tips: Save Your App!