Fiches de sécurité: Nodejs



Beaucoup a déjà été dit sur la popularité de NodeJS. L'augmentation du nombre d'applications est évidente - NodeJS est assez facile à apprendre, possÚde un grand nombre de bibliothÚques, ainsi qu'un écosystÚme en développement dynamique.

Nous avons préparé des recommandations pour les développeurs NodeJS basées sur les feuilles de triche OWASP pour vous aider à anticiper les problÚmes de sécurité lors du développement d'applications.

Les recommandations de sĂ©curitĂ© pour les applications NodeJS peuvent ĂȘtre rĂ©parties dans les catĂ©gories suivantes:

  • SĂ©curitĂ© lors du dĂ©veloppement d'applications;
  • SĂ©curitĂ© du serveur;
  • SĂ©curitĂ© de la plateforme;


Sécurité du développement d'applications


Évitez l'enfer des rappels L'

utilisation des fonctions de rappel (rappels) est l'une des plus grandes forces de NodeJS, cependant lors de l'imbrication des rappels, vous pouvez facilement oublier de gĂ©rer l'erreur dans l'une des fonctions. Une façon d'Ă©viter l'enfer de rappel est d'utiliser des promesses. MĂȘme si le module que vous utilisez ne prend pas en charge l'utilisation des promesses, vous pouvez toujours utiliser Promise.promisifyAll (). Mais mĂȘme en utilisant des promesses, il vaut la peine de faire attention Ă  la nidification. Pour Ă©viter complĂštement l'erreur d'enfer de rappel, respectez une chaĂźne de promesses «plate».

Exemple d'enfer de rappel:

function func1(name, callback) {
   setTimeout(function() {
      // operations
   }, 500);
}
function func2(name, callback) {
   setTimeout(function() {
      // operations
   }, 100);
}
function func3(name, callback) {
   setTimeout(function() {
      // operations
   }, 900);
}
function func4(name, callback) {
   setTimeout(function() {
      // operations
   }, 3000);
}

func1("input1", function(err, result1){
   if(err){
      // error operations
   }
   else {
      //some operations
      func2("input2", function(err, result2){
         if(err){
            //error operations
         }
         else{
            //some operations
            func3("input3", function(err, result3){
               if(err){
                  //error operations
               }
               else{
                  // some operations
                  func4("input 4", function(err, result4){
                     if(err){
                        // error operations
                     }
                     else {
                        // some operations
                     }
                  });
               }
            });
         }
      });
   }
});

MĂȘme code en utilisant des promesses de chaĂźne plate:

function func1(name, callback) {
   setTimeout(function() {
      // operations
   }, 500);
}
function func2(name, callback) {
   setTimeout(function() {
      // operations
   }, 100);
}
function func3(name, callback) {
   setTimeout(function() {
      // operations
   }, 900);
}
function func4(name, callback) {
   setTimeout(function() {
      // operations
   }, 3000);
}

func1("input1")
   .then(function (result){
      return func2("input2");
   })
   .then(function (result){
      return func3("input3");
   })
   .then(function (result){
      return func4("input4");
   })
   .catch(function (error) {
      // error operations
   });

Limitez la taille de la demande. L'

analyse du corps de la demande peut ĂȘtre une opĂ©ration gourmande en ressources. Si vous ne limitez pas la taille de la demande, les attaquants pourront envoyer des demandes suffisamment volumineuses pouvant remplir tout l'espace disque ou Ă©puiser toutes les ressources du serveur, mais en mĂȘme temps, limiter la taille de la demande pour tous les cas peut ĂȘtre incorrect, car il existe des demandes, telles que le tĂ©lĂ©chargement d'un fichier. Par consĂ©quent, il est recommandĂ© de dĂ©finir des limites pour diffĂ©rents types de contenu. Par exemple, en utilisant le framework express, cela peut ĂȘtre implĂ©mentĂ© comme suit:

app.use(express.urlencoded({ limit: "1kb" }));
app.use(express.json({ limit: "1kb" }));
app.use(express.multipart({ limit:"10mb" }));

Il convient de noter qu'un attaquant peut modifier le type de contenu de la demande et contourner les restrictions, il est donc nĂ©cessaire de vĂ©rifier si le contenu de la demande correspond au type de contenu spĂ©cifiĂ© dans l'en-tĂȘte de la demande. Si la vĂ©rification du type de contenu affecte les performances, vous ne pouvez vĂ©rifier que certains types ou requĂȘtes dont la taille est supĂ©rieure Ă  une certaine taille.

Ne pas bloquer la boucle d'événement

Un composant important du langage est la boucle d'événement, qui vous permet simplement de changer le contexte d'exécution sans attendre la fin de l'opération. Cependant, il existe des opérations de blocage dont l'exécution NodeJS doit attendre avant de continuer avec le code. Par exemple, la plupart des méthodes synchrones bloquent:

const fs = require('fs');
fs.unlinkSync('/file.txt');

Il est recommandé d'effectuer de telles opérations de maniÚre asynchrone:

const fs = require('fs');
fs.unlink('/file.txt', (err) => {
    if (err) throw err;
});

Dans le mĂȘme temps, n'oubliez pas que le code restant aprĂšs l'appel asynchrone sera exĂ©cutĂ© sans attendre la fin de l'opĂ©ration prĂ©cĂ©dente.

Par exemple, dans le code ci-dessous, le fichier sera supprimé avant sa lecture, ce qui peut conduire à une condition de concurrence critique.

const fs = require('fs');
fs.readFile('/file.txt', (err, data) => {
  // perform actions on file content
});
fs.unlinkSync('/file.txt');

Pour éviter cela, vous pouvez écrire toutes les opérations dans une fonction non bloquante:

const fs = require('fs');
fs.readFile('/file.txt', (err, data) => {
  // perform actions on file content
  fs.unlink('/file.txt', (err) => {
    if (err) throw err;
  });
});

VĂ©rifier les champs de saisie La

vérification des champs de saisie est un élément important de la sécurité de toute application. Les erreurs de validation peuvent rendre votre application vulnérable immédiatement à de nombreux types d'attaques: injection sql, xss, injection de commande et autres. Pour simplifier la validation des formulaires, vous pouvez utiliser des packages de validateur, mongo-express-sanitize.

Échapper aux donnĂ©es utilisateur

L'une des rÚgles qui vous aidera à vous protéger contre les attaques xss est de protéger les données utilisateur. Vous pouvez utiliser la bibliothÚque escape-html ou node-esapi pour cela.

Conserver les journaux

En outre, cela aidera Ă  dĂ©boguer les erreurs, la journalisation peut ĂȘtre utilisĂ©e pour rĂ©pondre aux incidents. Vous pouvez en savoir plus sur la nĂ©cessitĂ© de vous connecter ici.. Winston et Bunyan sont l'un des packages de journalisation NodeJS les plus populaires. L'exemple ci-dessous montre comment utiliser Winston pour gĂ©nĂ©rer des journaux sur la console et le fichier:

var logger = new (Winston.Logger) ({
    transports: [
        new (winston.transports.Console)(),
        new (winston.transports.File)({ filename: 'application.log' })
    ],
    level: 'verbose'
});

ContrÎlez le cycle des événements

Si votre serveur se trouve dans des conditions de trafic réseau intense, les utilisateurs peuvent rencontrer des difficultés avec la disponibilité de votre service. Il s'agit essentiellement d'une attaque DoS. Dans ce cas, vous pouvez suivre le temps de réponse et, s'il dépasse le temps spécifié, envoyer un message au 503 Server Too Busy. Le module toobusy-js peut vous aider.

Un exemple d'utilisation du module:

var toobusy = require('toobusy-js');
var express = require('express');
var app = express();
app.use(function(req, res, next) {
    if (toobusy()) {
        // log if you see necessary
        res.send(503, "Server Too Busy");
    } else {
    next();
    }
});

Prenez des précautions contre la force brutale.

LĂ  encore, les modules viennent Ă  la rescousse. Par exemple, express-brute ou express-bouncer. Exemple d'utilisation:

var bouncer = require('express-bouncer');
bouncer.whitelist.push('127.0.0.1'); // whitelist an IP address
// give a custom error message
bouncer.blocked = function (req, res, next, remaining) {
    res.send(429, "Too many requests have been made. Please wait " + remaining/1000 + " seconds.");
};
// route to protect
app.post("/login", bouncer.block, function(req, res) {
    if (LoginFailed){  }
    else {
        bouncer.reset( req );
    }
});

L'utilisation de CAPTCHA est une autre contre-mesure de force brute courante. Un module fréquemment utilisé pour aider à implémenter CAPTCHA est svg-captcha.

Utiliser des jetons CSRF

L'un des moyens les plus fiables de se protĂ©ger contre les attaques CSRF consiste Ă  utiliser un jeton CSRF. Le jeton doit ĂȘtre gĂ©nĂ©rĂ© avec une entropie Ă©levĂ©e, strictement vĂ©rifiĂ© et liĂ© Ă  la session de l'utilisateur. Pour garantir le fonctionnement du token CSRF, vous pouvez utiliser le module csurf.

Exemple d'utilisation:

var csrf = require('csurf');
csrfProtection = csrf({ cookie: true });
app.get('/form', csrfProtection, function(req, res) {
    res.render('send', { csrfToken: req.csrfToken() })
})
app.post('/process', parseForm, csrfProtection, function(req, res) {
    res.send('data is being processed');
});

N'oubliez pas d'ajouter le jeton au champ caché de la page:

<input type="hidden" name="_csrf" value="{{ csrfToken }}">

Vous pouvez en savoir plus sur les jetons CSRF dans notre article .

Supprimez les routes inutiles. L'

application Web ne doit pas contenir de pages qui ne sont pas utilisĂ©es par les utilisateurs, car cela peut augmenter la surface d'attaque. Par consĂ©quent, toutes les routes d'API inutilisĂ©es doivent ĂȘtre dĂ©sactivĂ©es. Vous devez particuliĂšrement faire attention Ă  cette question si vous utilisez des frameworks Sails ou Feathers, car ils gĂ©nĂšrent automatiquement des points de terminaison API.

Protégez-vous contre HPP (HTTP Parameter Pollution)

Par défaut, express ajoute tous les paramÚtres de la demande à un tableau. OWASP recommande d'utiliser le module hpp, qui ignore toutes les valeurs des paramÚtres de req.query et / ou req.body et sélectionne simplement la derniÚre valeur parmi celles en double.

var hpp = require('hpp');
app.use(hpp());

Surveillez les valeurs renvoyées.

Par exemple, la table utilisateur peut stocker des données importantes: mot de passe, adresse e-mail, date de naissance, etc. Par conséquent, il est important de renvoyer uniquement les données nécessaires.

Par exemple:

 exports.sanitizeUser = function(user) {
  return {
    id: user.id,
    username: user.username,
    fullName: user.fullName
  };
};

Utilisez des descripteurs

Utilisez des descripteurs pour décrire le comportement d'une propriété pour diverses opérations: inscriptible - s'il est possible de modifier la valeur d'une propriété, énumérable - s'il est possible d'utiliser une propriété dans une boucle for..in, configurable - s'il est possible d'écraser une propriété. Il est recommandé de faire attention aux propriétés répertoriées, car lors de la définition de la propriété d'un objet, tous ces attributs sont définis sur true par défaut. Vous pouvez modifier la valeur des propriétés comme suit:

var o = {};
Object.defineProperty(o, "a", {
    writable: true,
    enumerable: true,
    configurable: true,
    value: "A"
});

Utiliser les ACL

Acl peut aider à différencier l'accÚs aux données en fonction des rÎles. Par exemple, l'ajout d'une autorisation ressemble à ceci:

// guest is allowed to view blogs
acl.allow('guest', 'blogs', 'view')
// allow function accepts arrays as any parameter
acl.allow('member', 'blogs', ['edit', 'view', 'delete'])

Catch uncaughtException

Par dĂ©faut, en cas d'exception non interceptĂ©e, NodeJS lĂšvera la trace de pile actuelle et terminera le thread d'exĂ©cution. Cependant, NodeJS vous permet de personnaliser ce comportement. En cas d'exception non interceptĂ©e, un Ă©vĂ©nement uncaughtException est dĂ©clenchĂ©, qui peut ĂȘtre interceptĂ© Ă  l'aide de l'objet de processus:

process.on("uncaughtException", function(err) {
    // clean up allocated resources
    // log necessary error details to log files
    process.exit(); // exit the process to avoid unknown state
});

Il convient de se rappeler que lorsqu'une exception uncaughtException se produit, il est nécessaire d'effacer toutes les ressources allouées (par exemple, les descripteurs de fichiers et les gestionnaires) avant de terminer le processus Z afin d'éviter des erreurs imprévues. Il est fortement déconseillé que le programme continue de s'exécuter si une exception uncaughtException se produit.

En outre, lors de l'affichage de messages d'erreur, l'utilisateur ne doit pas divulguer d'informations détaillées sur les erreurs, telles que la trace de la pile.

Sécurité du serveur


DĂ©finissez des indicateurs pour les en-tĂȘtes lorsque vous travaillez avec des cookies.

Plusieurs indicateurs peuvent aider Ă  vous protĂ©ger contre les attaques telles que xss et csrf: httpOnly, qui empĂȘche l'accĂšs aux cookies via javascript; SĂ©curisĂ© - permet d'envoyer des cookies uniquement via HTTPS et SameSite, ce qui dĂ©termine la capacitĂ© de transfĂ©rer des cookies vers une ressource tierce.

Exemple d'utilisation:

var session = require('express-session');
app.use(session({
    secret: 'your-secret-key',
    key: 'cookieName',
    cookie: { secure: true, httpOnly: true, path: '/user', sameSite: true}
}));

DĂ©finir des en-tĂȘtes HTTP pour la sĂ©curitĂ©

Les Ă©lĂ©ments suivants sont des en-tĂȘtes et des exemples de la façon de les connecter pour vous aider Ă  vous protĂ©ger contre un certain nombre d'attaques courantes. Les en-tĂȘtes sont dĂ©finis Ă  l'aide du module casque

‱ Strict-Transport-Security: HTTP Strict Transport Security (HSTS) indique au navigateur que l'application n'est accessible que via HTTPS

app.use(helmet.hsts()); // default configuration
app.use(helmet.hsts("<max-age>", "<includeSubdomains>")); // custom configuration

‱ X-Frame-Options: dĂ©termine si la page peut ĂȘtre utilisĂ©e dans le cadre, l'iframe, l'incorporation ou l'objet

app.use(hemlet.xframe()); // default behavior (DENY)
helmet.xframe('sameorigin'); // SAMEORIGIN
helmet.xframe('allow-from', 'http://alloweduri.com'); //ALLOW-FROM uri

‱ Protection X-XSS: permet au navigateur d'arrĂȘter le chargement de la page s'il dĂ©tecte une attaque XSS rĂ©flĂ©chie.

var xssFilter = require('x-xss-protection');
app.use(xssFilter());

‱ X-Content-Type-Options: utilisĂ© pour empĂȘcher les attaques utilisant des types MIME

app.use(helmet.noSniff());

‱ Content-Security-Policy: empĂȘche les attaques telles que XSS et les attaques par injection de donnĂ©es

const csp = require('helmet-csp')
app.use(csp({
   directives: {
       defaultSrc: ["'self'"],  // default value for all directives that are absent
       scriptSrc: ["'self'"],   // helps prevent XSS attacks
       frameAncestors: ["'none'"],  // helps prevent Clickjacking attacks
       imgSrc: ["'self'", "'http://imgexample.com'"],
       styleSrc: ["'none'"]
    }
}))

‱ Cache-Control et Pragma: pour gĂ©rer la mise en cache, cet en-tĂȘte en particulier peut ĂȘtre utile pour les pages contenant des donnĂ©es sensibles. Cependant, n'oubliez pas que la dĂ©sactivation de la mise en cache sur toutes les pages peut affecter les performances.

app.use(helmet.noCache());

‱ X-Download-Options: l'en-tĂȘte empĂȘche Inter Explorer d'exĂ©cuter les fichiers tĂ©lĂ©chargĂ©s

app.use(helmet.ieNoOpen());

‱ Expect-CT: Transparence du certificat - un mĂ©canisme crĂ©Ă© pour rĂ©soudre certains problĂšmes avec l'infrastructure de certificat SSL, cet en-tĂȘte indique au navigateur la nĂ©cessitĂ© d'une vĂ©rification de certificat supplĂ©mentaire dans les journaux CT

var expectCt = require('expect-ct');
app.use(expectCt({ maxAge: 123 }));
app.use(expectCt({ enforce: true, maxAge: 123 }));
app.use(expectCt({ enforce: true, maxAge: 123, reportUri: 'http://example.com'}));

‱ X-Powered-By: un en-tĂȘte facultatif utilisĂ© pour indiquer la technologie utilisĂ©e sur le serveur. Vous pouvez masquer cet en-tĂȘte comme suit:

app.use(helmet.hidePoweredBy());

De plus, vous pouvez modifier la valeur pour masquer des informations réelles sur les technologies que vous utilisez:

app.use(helmet.hidePoweredBy({ setTo: 'PHP 4.2.0' }));

Sécurité de la plateforme


Mettre Ă  jour vos packages La

sécurité de votre application dépend de la sécurité des packages que vous utilisez, il est donc important d'utiliser la derniÚre version du package. Pour vous assurer que le package que vous utilisez ne contient pas de vulnérabilités connues, vous pouvez utiliser la liste spéciale OWASP . Vous pouvez également utiliser la bibliothÚque qui vérifie les packages pour les vulnérabilités connues Retire.js.

N'utilisez pas de fonctions dangereuses.

Il existe des fonctions qu'il est recommandé d'éliminer dans la mesure du possible. Parmi ces fonctions se trouve eval (), qui exécute une chaßne prise comme argument. En combinaison avec l'entrée utilisateur, l'utilisation de cette fonction peut entraßner des vulnérabilités dans l'exécution de code à distance, car pour des raisons similaires, l'utilisation de child_process.exec est également dangereuse, car la fonction transmet les arguments reçus à bin / sh.

En outre, il existe un certain nombre de modules que vous devez utiliser avec prudence. Par exemple, le module fs pour travailler avec des fichiers. Si, d'une certaine maniÚre, l'entrée utilisateur générée est transmise à une fonction, votre application peut devenir vulnérable à l'inclusion d'un fichier local et d'une traversée de répertoire.

Le module vm, qui fournit une API pour la compilation et l'exĂ©cution de code sur une machine virtuelle V8, ne doit ĂȘtre utilisĂ© que dans le bac Ă  sable.

Ici, vous pouvez vous familiariser avec d'autres fonctions qui peuvent rendre votre application dangereuse.

Soyez prudent en utilisant des expressions réguliÚres.

Une expression rĂ©guliĂšre peut ĂȘtre Ă©crite afin que vous puissiez arriver Ă  une situation oĂč l'expression va croĂźtre de façon exponentielle, ce qui peut conduire Ă  un dĂ©ni de service. Ces attaques sont appelĂ©es ReDoS. Il existe plusieurs outils pour vĂ©rifier si les expressions rĂ©guliĂšres sont sĂ»res, dont l'un est le dĂ©tecteur vuln-regex.

Exécutez linter périodiquement

Pendant le dĂ©veloppement, il est difficile de garder Ă  l'esprit toutes les recommandations pour assurer la sĂ©curitĂ©, et s'il s'agit de dĂ©veloppement d'Ă©quipe, il n'est pas facile de se conformer aux rĂšgles par tous les membres de l'Ă©quipe. À ces fins, il existe des outils d'analyse statique de la sĂ©curitĂ©. De tels outils, sans exĂ©cuter votre code, y recherchent des vulnĂ©rabilitĂ©s. De plus, les linters vous permettent d'ajouter des rĂšgles personnalisĂ©es pour trouver des endroits dans le code qui peuvent ĂȘtre vulnĂ©rables. Les linters les plus couramment utilisĂ©s sont ESLint et JSHint.

Utilisez le mode strict

Javascript a un certain nombre de fonctions non sĂ©curisĂ©es et obsolĂštes qui ne devraient pas ĂȘtre utilisĂ©es. Pour exclure la possibilitĂ© d'utiliser ces fonctions, un mode strict est Ă©galement fourni.

Adhérer aux principes généraux de sécurité

Les recommandations décrites se concentrent sur NodeJS, mais n'oubliez pas les principes généraux de sécurité à respecter quelle que soit la plateforme utilisée.

All Articles