MEAN est un socle technique composé de MongoDB, Express, Angular et Node.js. Ces quatre composants ont un point commun : le JavaScript ! La connaissance de cet unique langage de programmation vous permet désormais de créer une application web dynamique et moderne. Ce socle technique est donc largement utilisé par les développeurs « full stack » et c’est ainsi que de nombreuses applications MEAN viennent s’installer progressivement dans les SI des entreprises. Mais voilà ! Ce socle technique est-il suffisant pour développer des applications sécurisées qui seront amenées à manipuler des données sensibles (carte bancaire, santé…) ?
1. Architecture MEAN
L’architecture d’une application développée avec le socle MEAN est relativement simple. Elle est composée de 2 grandes parties :
- le front-end : Angular ;
- le back-end : Express, Node.js et MongoDB.
Figure 1
1.1 Angular
Angular est le framework de Google pour développer des applications web monopage (SPA = Single Page Application). Une application Angular est constituée de fichiers statiques (HTML, CSS et JS) que l’utilisateur doit télécharger depuis un serveur HTTP.
REACT est une alternative à Angular. Dans ce cas, le socle technique est appelé « MERN ».
1.2 Node.js
Node.js est un environnement d’exécution JavaScript offrant de nombreux outils aux développeurs, dont NPM (Node Package Manager). Le composant principal est le moteur JavaScript « V8 » créé par Google pour Chrome/Chromium. Les performances de ce moteur, parfois supérieures à PHP, permettent désormais de l’utiliser en entreprise.
JavaScript, TypeScript, ECMAScript, CoffeeScript… Il y a de quoi s’y perdre !
Il faut savoir que le standard ECMAScript définit les bases d’un langage de script (types, boucles, conditions…). Pour être conforme, un langage de script doit a minima implémenter les bases indiquées dans la norme.
Le JavaScript n’est qu’une implémentation par Mozilla du standard. Mais il y a beaucoup d’autres implémentations : TypeScript par Microsoft, ActionScript par Adobe.
1.3 Express
Express est un framework de développement pour Node.js. Il permet de développer rapidement en JavaScript des applications web. Express et Node.js constituent la première partie du backend.
Un guide officiel décrivant les bonnes pratiques de sécurité est disponible sur le site de l’éditeur [1].
Depuis février 2016, ce framework est soutenu par la Fondation Node.js [7] avec une forte contribution de la part d’IBM via StrongLoop.
1.4 MongoDB
MongoDB est une base de données NoSQL orientée document. La menace d’injection est toujours présente puisque « NoSQL » ne signifie malheureusement pas « No Injection » [2].
En complément, un ODM (Object Data Model), tel que Mongoose, est souvent utilisé pour simplifier l’accès à la base de données. Il permet également de prendre en charge des fonctions de chiffrement des données ou signature des données.
En comptant bien, l’architecture MEAN contient 3 composants (Angular + Express / Node.js + MongoDB). Nous pourrions rapidement en conclure que cette architecture est conforme à l’architecture « 3 tiers », la référence dans le domaine de la sécurité.
Mais voilà, 3 tiers ne signifie pas 3 composants, mais 3 pare-feux avec 3 zones réseau : présentation, métier et données.
La confusion est induite par l’application Angular qui constitue le front-end et s’exécute dans le navigateur de l’utilisateur. En termes de sécurité, seul le back-end sera pris en compte avec 2 zones réseaux : application (Express et Node.js) et données (MongoDB).
2. Express
Avant d’aller plus loin, il est nécessaire de comprendre certains concepts fondamentaux indispensables à la compréhension de la suite du dossier.
Express est un framework minimaliste qui permet de définir un pipeline pour traiter des requêtes HTTP. Ces requêtes sont traitées par des middlewares qui enrichissent au fur et à mesure la requête (objet req) et/ou la réponse HTTP (objet res).
Figure 2
Un middleware est un module développé pour effectuer une petite fonctionnalité. Par défaut, le framework Express contient quelques middlewares de base comme le routeur. Ils sont inclus nativement dans le framework. En complément, Express propose des middlewares additionnels que vous pouvez installer (exemple : express-session ou express-cookie).
Mais vous trouverez également de nombreux middlewares réalisés par des sociétés ou des particuliers, c’est notamment le cas de PassportJS. Avant d’utiliser un middleware, il est important de réaliser quelques points de contrôles : est-il toujours maintenu ? Y a-t-il des bogues de sécurité connus et non corrigés ? Utilise-t-il des frameworks avec une vulnérabilité connue ? ...
Pour démarrer rapidement un nouveau pipeline Express avec les principaux middlewares, il suffit d’utiliser l’outil Express Generator. Il crée automatiquement la structure minimum nécessaire au fonctionnement d’une application avec notamment :
- le dossier « Routes » contenant la logique de routage ;
- le dossier « Views » contenant les vues en fonction du moteur de rendu choisi (jade, pug…) ;
- le fichier « app.js » contenant le code principal de l’application.
Cette structure se conforme aisément au standard MVC (Model / View / Controller) pour structurer le code :
Figure 3
Le concept MVC (Model / View / Controller) est une manière de structurer le code source d’une application (Design Pattern). Il y a bien trois parties, mais ce n’est pas pour autant une architecture 3 tiers.
Vous voilà maintenant familiarisés avec Express ! Nous pouvons maintenant aborder la sécurité d’Express et les middlewares liés à la sécurité.
3. HTTPS Express
L’utilisation du protocole HTTPS est une mesure fondamentale qu’une application Express se doit d’implémenter. Pour cela, deux options s’offrent à vous :
- une terminaison TLS portée par une passerelle (reverse proxy) devant l’application ;
- une terminaison TLS portée directement par l’application.
À noter que ces deux solutions ne sont pas incompatibles entre elles et il est tout à fait envisageable de les combiner. Quelle que soit la solution retenue, il vous faudra un nom de domaine et un certificat pour dérouler la suite de l’article. Pour tester, un certificat auto-signé et une entrée dans le fichier host feront l’affaire.
3.1 Solution 1 : Terminaison TLS par proxy
Dans cette configuration, l’architecture sera la suivante :
Figure 4
3.1.1 Étape 1 : Configurer Nginx
La configuration ci-dessous permettra de transférer les en-têtes HTTP « X-forwarded-* » à l’application Express :
server {
listen *:443;
server_name proxy.example.com;
# TLS settings
ssl on;
ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
# modern configuration. tweak to your needs.
ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AE$
ssl_prefer_server_ciphers on;
location / {
# HTTPS Headers
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Express App
proxy_pass http://app.example.com:3000/;
}
}
3.1.2 Étape 2 : Configurer l’application
Par défaut, une application Express ne fonctionnera pas de manière optimale derrière un proxy. Par exemple, les entêtes HTTP ne seront pas pris en compte et un « secure cookie » ne pourra être transmis avec le protocole HTTP. Pour y remédier, il faut activer l’option « trust proxy » en indiquant l’adresse IP des proxies [3].
Les modifications à apporter dans le fichier app.js sont :
app.set('trust proxy', process.env.PROXY);
Ainsi, le trust proxy sera activé uniquement pour les requêtes venant des adresses IP des proxies mentionnées dans la liste. Les requêtes provenant d’autres sources ne sont pas bloquées pour autant.
L’activation du « trust proxy » a pour effet :
- la valeur de req.hostname est définie à partir de l’en-tête « X-Forwarded-Host » ;
- la valeur de req.protocol est définie à partir de l’en-tête « X-Forwarded-Proto », lui-même défini par le proxy. Ainsi, le proxy peut indiquer à l’application que le protocole utilisé est HTTPS alors que l’application est en HTTP ;
- les valeurs de req.ip et req.ips sont définies avec la liste des adresses IP présentes dans l’en-tête « X-Forwarded-For ». Req.ip contient la première adresse IP de la liste. L’option Trust Proxy est indispensable pour générer correctement les traces dans votre SIEM.
Les effets mentionnés ci-dessus ne sont valables que pour les adresses IP indiquées. Pour les requêtes venant d’autres adresses IP, les valeurs ne seront pas modifiées.
3.2 Solution 2 : Terminaison TLS par l’application
Par défaut, une application Express utilise le module HTTP pour créer le serveur qui recevra les requêtes. Mais il est très simple de modifier une application Express pour utiliser le module HTTPS de Node.js.
Par exemple, pour une application créée avec Express Generator, il faut modifier le script de lancement de l’application (bin/www) comme ceci :
/**
* Module dependencies.
*/
var fs = require('fs');
var https = require('https');
/**
* Create HTTP(s) server.
*/
// HTTPS setup
var options = {
// Private key
key: fs.readFileSync('./tls/key.pem'),
// Certificate
cert: fs.readFileSync('./tls/cert.pem'),
// Protocol : only TLS 1.2
secureProtocol: 'TLSv1_2_method',
// Cipher Suites
ciphers:"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256",
};
// Create HTTPS server
var server = https.createServer(options, app);
Dans la section Module dependencies, il faut ajouter les 2 modules nécessaires :
- fs pour lire les fichiers contenant la clé et le certificat ;
- https pour remplacer le module http.
Dans la section Create HTTP(s) server, il faut configurer le protocole TLS : clé privée, certificat et les options de chiffrement. Dans l’exemple ci-dessus, les suites de chiffrement sont conformes au profil « modern » défini par Mozilla [4].
4. Session Express
Express vous propose deux solutions pour conserver les informations relatives à l’utilisateur :
- Express Session : les informations sont conservées sur le serveur dans une base de données ;
- Cookie Session : les informations sont conservées dans un cookie.
4.1 Express Session
Le middleware « Express-Session » permet de conserver les données, liées à la session en cours, sur le back-end. Un cookie de session nommé « connect.sid » contiendra seulement l’identifiant de la session.
La suite de cet article décrit la réalisation du prototype, disponible sur GitHub. Pour le stockage des sessions, ce prototype utilise Redis, une base de données clé/valeur réputée pour sa rapidité. Le middleware « connect-redis » sera donc nécessaire.
4.1.1 Étape 1 : Installer les middlewares
$ npm install --save express-session connect-redis
4.1.2 Étape 2 : Configurer l’application
Éditer le fichier app.js pour y ajouter le code ci-dessous :
// Loading middleware
const Session = require('express-session');
const RedisStore = require('connect-redis')(Session);
// Session store setup
const SessionStore = new RedisStore({
host: process.env.REDISHOST,
port: process.env.REDISPORT,
pass: process.env.REDISPASSWORD
});
// Express Session middleware setup
app.use(Session({
store: SessionStore,
secret: process.env.COOKIESECRET,
resave: false,
saveUninitialized: false,
cookie:{path: '/', httpOnly: true, secure: true, maxAge: 600000, sameSite: 'strict'}
}));
Le paramètre Secret est indispensable, car les cookies de session sont obligatoirement signés. La structure d’un cookie est la suivante après avoir effectué un décodage HTML :
Figure 5
Ainsi, même si une personne malveillante « devine » l’identifiant de session d’un utilisateur, elle ne pourra pas créer un cookie valide, car elle ne connaît pas le secret nécessaire à la création de la signature.
Le paramètre saveUninitialized est un booléen dont la signification est la suivante :
- true : la session sera créée pour tout visiteur ce qui peut monopoliser des ressources inutilement sur le serveur ;
- false : la session (et le cookie) sera créée uniquement lorsque l’application initialisera la session en y enregistrant une donnée.
4.2 Cookie Session
Le middleware « cookie-session » est une alternative au middleware précédent. Aucune information ne sera stockée sur le back-end puisque toutes les données de la session seront stockées dans le cookie. Attention toutefois à ne pas y stocker trop d’informations, car la taille des cookies est limitée à 4 Ko.
4.2.1 Étape 1 : Installer les middlewares
$ npm install --save cookie-session
4.2.2 Étape 2 : Configurer le middleware
// Configure middleware
app.use(cookieSession({
name: 'session',
keys: new Keygrip([process.env.COOKIESECRET], 'SHA256', 'base64'),
// Cookie Options
path: '/',
httpOnly: true,
secure: true,
signed: true,
maxAge: 600000, // 10 minutes
sameSite: 'strict'
}));
Ce middleware ainsi configuré génère deux cookies à l’initialisation de la session :
- session : ce cookie contient toutes les données de la session au format JSON et encodé en base64 ;
- session.sig : ce cookie contient la signature du cookie de session. L’algorithme utilisé est le SHA256 encodé en base 64. L’algorithme par défaut est le SHA1.
5. CSRF
Une application MEAN se doit de se protéger contre les attaques de type CSRF [5]. Il est donc important de bloquer les requêtes qui seraient initiées à l’insu de l’utilisateur depuis un site internet malveillant.
La solution la plus efficace est d’utiliser l’attribut samesite sur l’ensemble de vos cookies et en particulier le cookie de session. C’est ce qui a été réalisé dans les prototypes précédents.
Même si largement supporté par les navigateurs [6], l’attribut samesite n’est pas encore validé par l’IETF. Pour garantir une protection avec les navigateurs ne supportant pas l’attribut samesite, il faudra implémenter une solution à base de jeton anti-csrf sur le back-end et le front-end.
5.1 Back-end
Au niveau du back-end, Express propose le middleware « CSurf » qui permet de générer des jetons anti-CSRF et d’en contrôler la validité.
Pour chaque requête entrante, le middleware génère un jeton qui sera ajouté à la réponse sous l’une des formes suivantes :
- dans un cookie généralement nommé « XSRF-TOKEN » ;
- dans un champ caché d’un formulaire ;
- dans une balise HTML meta.
La structure des jetons est la suivante :
Figure 6
Le secret utilisé pour signer le jeton doit être unique par utilisateur. Ainsi, un jeton généré pour un utilisateur ne pourra pas être utilisé pour valider une requête d’un autre utilisateur. Néanmoins, cela nécessite de conserver les secrets soit dans la session de l’utilisateur, soit dans un cookie nommé par défaut « _csrf ».
5.2 Front-end
Les applications web développées avec Angular supportent nativement les jetons anti-csrf. Le fonctionnement est le suivant :
- L’application web reçoit, du back-end, un cookie nommé « XSRF-TOKEN » contenant une valeur aléatoire. Ce cookie ne doit pas être en « HTTPonly », car l’application web doit être en mesure de le lire.
- Pour chaque requête HTTP demandant la modification des données (POST, PUT, DELETE…), le front-end récupère la valeur du cookie et l’injecte dans l’en-tête HTTP nommé « X-XSRF-TOKEN ».
Si les jetons anti-csrf étaient nativement supportés avec les premières versions d’Angular (module HTTP), les versions plus récentes (module HTTPClient) devront explicitement l’activer en important le module HttpClientXsrfModule.
Conclusion
Le socle technique MEAN ne vous assure pas une application sécurisée. Les développeurs doivent prendre connaissance des risques auxquels est exposée leur application. Ils devront alors implémenter les mesures adéquates au cours du développement. Et comme vous avez pu le constater tout au long de cet article, ce n’est pas si facile.
Express propose de nombreux middlewares pour assurer certaines fonctions de sécurité. Mais cela ne permet pas de couvrir l’ensemble des fonctions de sécurité, comme l’authentification, et vous devrez faire appel à des modules tiers : PassportJS, Helmet, Kraken, Node-ESAPI…
Références
[1] https://expressjs.com/en/advanced/best-practice-security.html
[2] https://www.owasp.org/index.php/Testing_for_NoSQL_injection
[3] https://expressjs.com/en/guide/behind-proxies.html
[4] https://wiki.mozilla.org/Security/Server_Side_TLS
[5] https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet
[6] https://caniuse.com/#feat=same-site-cookie-attribute
[7] https://nodejs.org/en/blog/announcements/foundation-express-news/