Je suis toujours très surpris d'entendre parler de la sécurité des applications frontend parce que précisément une application frontend s'exécute sur le périphérique de l'utilisateur et ne peut donc pas être sécurisée. Elle doit même être considérée comme un client potentiellement malveillant.
En effet, le code source de l'application étant à la disposition de l'utilisateur, il est possible de l'étudier et de le modifier à volonté afin d'en comprendre les mécanismes internes ou de récupérer toutes les données stockées sur le périphérique.
Je suis tombé sur de nombreux articles de diverses sources (Callstack, Jscrambler, Tabris, Nativescript, Reactnativecode) qui détaillaient les "techniques" pour sécuriser une application frontend en utilisant l'obfuscation, du chiffrement custom (XOR avec réutilisation de clé, etc...), et ainsi de suite.
Ce n'est pas la première fois que j'entends parler de recettes sur "l'écriture d'une application frontend sécurisée": Il peut être dangereux et contre-productif d'essayer de sécuriser une application avec des techniques inefficaces. Voici pourquoi.
TLDR;
L’envoi des informations d’authentification se fait de manière sécurisée au travers d’une connexion SSL, la confidentialité des communications est donc assurée dans la plupart des cas.
Ajouter une couche de chiffrement basique pour le mot de passe avec un simple XOR et une clé réutilisée pour chaque authentification de chaque client est inutile pour plusieurs raisons:
Rajouter une couche de chiffrement peut s’avérer être une bonne idée pour éviter la compromission des données dans le cas d’une attaque Man In The Middle avec un faux certificat SSL.
Pour un chiffrement solide, il sera nécessaire à minima:
Cela revient plus ou moins à implémenter une version custom de SSL dans son application et implémenter son propre système cryptographique n'est jamais une bonne idée.
When you try to reinvent the wheel..
Il est bien plus facile d’utiliser d’autres techniques de sécurisation tel que le SSL Pinning pour se prévenir des attaque MITM.
Dernier point, pour un maximum de sécurité, il est conseillé de générer des tokens ayant une durée de validitée courte pour limiter les dégats causés par une éventuelle compromission.
Lorsqu’il est nécessaire de stocker des données sensibles dans une application frontend, il est préférable d’utiliser les mécanismes mis à disposition par les créateurs de l’environnement de développement.
Par exemple, dans une application mobile avec React Native, nous pouvons utiliser la Keychain d’Apple ou le Keystore d’Android. Ces mécanismes rendent plus difficile l’extraction de données sensibles depuis un device mais ils ne doivent pas non plus être considérés comme inviolables. (Eg: Apple Keychain exploit)
Dans tous les cas, il est inutile de rajouter une couche de chiffrement supplémentaire réalisée avec une clé prédictible car un attaquant peut faire du reverse engineering sur l’application pour retrouver la clé. Ou encore plus facilement, simplement accéder à la clé stockée en mémoire.
Cela est contre-productif va consommer inutilement des ressources CPU pour le chiffrement/déchiffrement et donc drainer la batterie.
Bien que je puisse comprendre que les développeurs puisse vouloir compliquer la tâche de reverse engineering d'une application, l’obfuscation de doit jamais être considérée comme une pratique de sécurisation.
Elle peut au maximum décourager certains attaquants mais quelqu’un de motivé pourra toujours analyser et comprendre le fonctionnement de l'application.
Surtout si un obfuscateur open-source est utilisé car celui-ci est donc connu et des dé-obfuscateurs doivent certainement déjà exister.
De plus, l'obfuscation va rendre le code très difficile à interpréter et optimiser par les différents moteurs Javascript et il en résultera une baisse significative des performances de l’application.
Benchmark réalisé avec l'obfuscateur de react-native-obfuscating-transformer
Comme nous l’avons vu, une application frontend ne peut pas être sécurisée. Comme il est impossible d’avoir le contrôle sur le terminal du client, il est impossible de s’assurer que celui-ci n’est pas compromis.
C’est sur le backend que la majeure partie des éléments de sécurité doivent être mis en place. Il n’y a pas de recette magique pour sécuriser un backend, c’est un ensemble de bonnes pratiques de programmation qui permettra d’arriver à un résultat optimal.
Depuis le corps d’une requête HTTP, en passant par les headers ou encore les cookies, toutes ces informations qui peuvent être manipulées par l’utilisateur doivent être considérées comme potentiellement malicieuses.
L’utilisation naïve des saisies des utilisateurs peut amener à toutes sortes d’attaques:
Il est toujours nécessaire de vérifier et sanitiser ces données avant de les utiliser dans une application.
Les attaques par déni de service tentent de rendre une application indisponible.
Il est possible, par exemple, d'envoyer de très grandes requêtes en JSON, ce qui peut ralentir ou même rendre indisponible votre application.
Atténuer l'attaque: limiter la taille des requêtes dans les couches basses de votre application, de préférence directement dans les couches réseau.
Si votre backend est écrit en Node.js, il est également nécessaire d'être vigilant lors de la création de nouvelles promesses.
En effet, une Promesse est automatiquement envoyée à l'Event Loop et il est alors impossible de la retirer avant sa résolution ou son rejet.
Un attaquant peut alors envoyer beaucoup de requêtes sur une route générant des promesses et donc saturer l'Event Loop.
Atténuer l'attaque: implémenter un système de limite de requêtes simultanées utilisant uniquement des callbacks. Développer avec des callbacks c'est plus compliqué, mais les callbacks ne sont que des pointeurs vers des fonctions n'utilisant aucune ressource jusqu'à ce qu'ils soient invoqués. N'utilisez les promesses qu'après le système de limite de requêtes.
Afin d’éviter un bruteforce de l’authentification d’un utilisateur, il est nécessaire d’introduire une limite au nombre de tentative de connexion.
Cette limite peut prendre la forme d’un blocage après X tentatives rajouté à la route d’authentification.
En 2019, il y avait encore des entreprises qui stockent les mots de passes de leurs utilisateurs en clair.
Cette pratique doit être évitée à tout prix afin de protéger vos utilisateurs en cas de fuite de données de votre application. Pas seulement pour votre propre application : la majorité des utilisateurs réutilisent le même mot de passe pour plusieurs comptes. L'impact d'une fuite de mot de passe peut être catastrophique, tant pour vos utilisateurs que pour l'image de votre entreprise.
Les mots de passe doivent être stockés à l'aide d'une fonction cryptographique unidirectionnelle (ou fonction de hachage).
Le choix de la fonction de hachage doit être basé sur un algorithme robuste tel que bcrypt par exemple. Si vous le pouvez, renforcez les mots de passe faibles en utilisant du key stretching, et pour une couche de sécurité supplémentaire, vous pouvez également utiliser un salt et un pepper sur les mots de passes.
Il y a énormément d’attaques possibles sur un backend, et la plupart sont peu ou pas connues. Certaines sont particulièrement difficiles à détecter et à prévenir :
Une simple comparaison de deux chaînes de caractères peut se donner lieu à une attaque temporelle et permettre à un attaquant de deviner un mot de passe ou un token.
Mais encore l’utilisation d’une librairie d’expression régulières vulnérable à une attaque ReDoS.
C’est pourquoi la sécurisation d’un backend n’est pas une tâche à prendre à la légère et doit être confiée à des experts en sécurité pour former les développeurs mais aussi auditer le code afin de s’assurer d’avoir le minimum de failles possibles car la sécurité parfaite n’existe pas.
Lors du développement d'une application, la sécurité doit être prise en compte du début à la fin et la réflexion doit couvrir l'ensemble du périmètre de l'application, du backend au frontend, y compris les canaux de communication et l'hébergement.
La sécurité coûte cher et est donc souvent négligée. C'est pourquoi il est préférable d'utiliser des frameworks et des backends conçus par des ingénieurs possédant les compétences et connaissances nécessaires pour assurer une sécurité suffisante aux utilisateurs finaux.
J'aimerais remercier toute l'équipe de Kuzzle qui m'a aidé à rédiger cet article et en particulier Sébastien Cottinet et Yannick Combes pour leur expertise en sécurité et cryptographie.