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() {
}, 500);
}
function func2(name, callback) {
setTimeout(function() {
}, 100);
}
function func3(name, callback) {
setTimeout(function() {
}, 900);
}
function func4(name, callback) {
setTimeout(function() {
}, 3000);
}
func1("input1", function(err, result1){
if(err){
}
else {
func2("input2", function(err, result2){
if(err){
}
else{
func3("input3", function(err, result3){
if(err){
}
else{
func4("input 4", function(err, result4){
if(err){
}
else {
}
});
}
});
}
});
}
});
MĂȘme code en utilisant des promesses de chaĂźne plate:function func1(name, callback) {
setTimeout(function() {
}, 500);
}
function func2(name, callback) {
setTimeout(function() {
}, 100);
}
function func3(name, callback) {
setTimeout(function() {
}, 900);
}
function func4(name, callback) {
setTimeout(function() {
}, 3000);
}
func1("input1")
.then(function (result){
return func2("input2");
})
.then(function (result){
return func3("input3");
})
.then(function (result){
return func4("input4");
})
.catch(function (error) {
});
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Ă©nementUn 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) => {
});
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) => {
fs.unlink('/file.txt', (err) => {
if (err) throw err;
});
});
VĂ©rifier les champs de saisie LavĂ©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 utilisateurL'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 journauxEn 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énementsSi 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()) {
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');
bouncer.blocked = function (req, res, next, remaining) {
res.send(429, "Too many requests have been made. Please wait " + remaining/1000 + " seconds.");
};
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 CSRFL'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 descripteursUtilisez 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 ACLAcl peut aider à différencier l'accÚs aux données en fonction des rÎles. Par exemple, l'ajout d'une autorisation ressemble à ceci:
acl.allow('guest', 'blogs', 'view')
acl.allow('member', 'blogs', ['edit', 'view', 'delete'])
Catch uncaughtExceptionPar 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) {
process.exit();
});
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 HTTPSapp.use(helmet.hsts());
app.use(helmet.hsts("<max-age>", "<includeSubdomains>"));
âą X-Frame-Options: dĂ©termine si la page peut ĂȘtre utilisĂ©e dans le cadre, l'iframe, l'incorporation ou l'objetapp.use(hemlet.xframe());
helmet.xframe('sameorigin');
helmet.xframe('allow-from', 'http://alloweduri.com');
âą 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 MIMEapp.use(helmet.noSniff());
âą Content-Security-Policy: empĂȘche les attaques telles que XSS et les attaques par injection de donnĂ©esconst csp = require('helmet-csp')
app.use(csp({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
frameAncestors: ["'none'"],
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Ă©sapp.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 CTvar 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 LasĂ©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Ă©riodiquementPendant 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 strictJavascript 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.