NOSQL, NO Security ?

Sécurité des applications
NOSQL, NO Security ?

Régulièrement nous entendons que les bases NOSQL ne peuvent souffrir d'injections SQL. Sur le principe je dis oui, pas d'injection SQL dans les bases NOSQL. Mais quid des injections NOSQL ? :)

Nous allons commencer par rappeler rapidement ce qu'est :

1. Une base NOSQL           2. Une injection

Puis nous pourrons voir comment empêcher les injections NOSQL.

Qu'est-ce qu'une base NOSQL ?

Ce type de bases de données est très à la mode pour la gestion des sites nécessitant de la performance (entre autres) et a été adopté par quelques-uns des plus gros sites de la planète (Amazon, Google, Facebook, Twitter, ...).

La grosse différence entre une base NOSQL et une base SQL, est que dans les bases NOSQL le stockage n'est plus effectué comme dans les bases relationnelles avec un modèle bien défini, mais sous la forme de tableaux associatifs (type clef/valeurs) ou orienté colonne (le nombre de colonnes peut varier) ou orienté document (les données sont stockées sous la forme de documents XML, JSON, ...) ou encore orienté graphe (basé sur la théorie des graphes).

On va retrouver par exemple les bases suivantes

Au final on va donc se retrouver avec un stockage de données sous la forme choisie par la base (exemple en JSON/XML/...) avec en  amont un moteur permettant de transformer ce que l'on lui envoie. Ce qui veut dire que l'on dispose alors d'un interpréteur ou tout autre élément permettant de faire des requêtes...

Qu'est-ce qu'une injection SQL ?

L'injection fait partie des failles les plus courantes et les plus critiques. Elle est en pôle position dans le Top 10 OWASP. Une injection c'est l'envoi de données malveillantes (via un en-tête, un champ de formulaire, ...) à un programme qui se comporte alors de façon non attendue pour potentiellement finir par la prise de contrôle du serveur de bases de données dans le cas d'une injection SQL.

Il ne faut pas oublier qu'il existe de nombreuses formes d'injections (commandes, XPath, XML, SQL, LDAP, Hibernate, JSON, ...), car tout programme utilisant un interpréteur est vulnérable...

Prenons un morceau de code moisi. Par exemple, ce code Java, adapté de CWE-89 :

NOSQL1

Nous voyons que si dans la variable userName on retrouve un truc du genre : 'test' OR '1'='1' -- on finit avec une requête SQL complètement chamboulée :

SELECT* FROM items where owner ='test' OR '1'='1'

Soit, au final, l'équivalent de la requête suivante :

SELECT* FROM items

On sélectionnera donc tous les éléments, ce qui ne correspond vraissemblablement pas à l'intention initiale...

La seule solution pour empêcher les injections consiste à valider l'ensemble des données que l'on s'apprête à manipuler avec le programme.

Il existe une "solution" supplémentaire dans le cas de SQL, supportée par la majorité des frameworks. Cela consiste en l'utilisation  des requêtes paramétrées (ou requêtes préparées). Cela a pour effet, lorsque cette "solution" est bien utilisée, de neutraliser l'injection.

Le code moisi précédent s'écrit donc alors comme suit :

En Java -

NOSQL2

Toute tentative d'envoi de la chaine "test' OR '1'='1' --" arrêtera l'interprétation de la requête.

Voyons maintenant comment tout cela fonctionne dans une base NOSQL. Nous avons sélectionné la base MongoDB pour plusieurs raisons :

    1. Si l'on regarde le classement des bases de données utilisées, MongoDB est situé au 5ème rang, après les bases les plus connues... (http://db-engines.com/en/ranking).
    2. On s'aperçoit sur Google Trends que les requêtes sur cette base en France ne faiblissent pas.

NOSQL3

  1. Et puis aussi il est à noter que MongoDB stocke ses données sous un format proche de JSON (du JSON CodeWScope). Ce qui tout naturellement conduit à voir qu'elle est sûrement très utilisée dans les développements mobiles et récents en REST.

Comment fonctionne une base NOSQL ?

Contrairement donc à une base SQL, une base NOSQL ne comporte pas de "réelle" structure (ie pas de schéma de tables/bases prédéterminées) comme ce que l'on peut s'attendre. Si on regarde du côté de MongoDB, on va retrouver des collections d'objets pouvant comporter des champs différents.

Exemple de stockage de données en JSON :

NOSQL4

Les différents langages proposent tous des frameworks / API permettant de lancer des requêtes vers les bases MongoDB. Il existe un ensemble de packages Java sous com.mongodb permettant de donner l'essentiel des classes et méthodes d'accès en Java.

En reprenant le principe du code moisi précédent, nous pourrions écrire cela :

NOSQL5

Or dans cet exemple la donnée de la chaîne d'authentification provient directement du client et est passée à l'évaluation via la méthode JSON.parse. La méthode envoie ensuite directement à MongoDB la chaîne pour effectuer la recherche et extrait alors les éléments.

Mais que se passe-t-il si l'utilisateur parvient à falsifier la chaîne depuis le client comme cela :

NOSQL6

A ce moment-là, MongoDB va rechercher soit un username mmichu, soit, un compte avec le mot de passe michu.

Ce type de code est assez courant dans des applications serveurs recevant des données JSON depuis des mobiles par exemple. Il n'est pas courant que le développeur se dise que le client mobile va envoyer des données non conformes. Néanmoins, si vous exposez sur un point d'accès de type REST/JSON votre base de cette manière, il est possible de passer outre l'authentification, voire de faire exécuter ce que l'on veut (ou presque) à la base MongoDB.

La bonne solution consiste à ne pas utiliser le principe de parsing JSON, mais à créer de toute pièce votre requête. Un peu comme en SQL avec les requêtes paramétrées. Exemple pour le cas précédent s'écrira :

NOSQL7

Vous avez empêché l'injection NOSQL sur votre fonction d'authentification. Il convient donc de correctement utiliser les méthodes disponibles dans le Framework et de ne pas utiliser de fonction d'évaluation pour éviter les problèmes.

Il existe d'autres formes d'injections sur ce type de base de données que nous n'avons pas évoqué, en particulier l'injection de JavaScript serveur. Cela fera partie d'un prochain billet, mais de manière plus globale. En effet, JavaScript est de plus en plus utilisé côté serveur (Node.JS par exemple) et client (AngularJS par exemple) et divers points sont à prendre en compte.

En résumé, les préconisations sont les suivantes pour lutter contre les injections sur une base MongoDB :

  • Valider fortement vos données comme dans le cas de n'importe quelle utilisation de données provenant de source externe à votre application.
  • Utiliser les méthodes de l'API n'utilisant pas le principe des requêtes dynamiques.
  • Activer l'authentification MongoDB et gérer de manière stricte des rôles utilisateurs sur les documents.
  • Désactiver l'API REST (elle est bien désactivée par défaut) pour privilégier l'accès via un client BSON (et donc exposer si besoin une API REST via une application Web).
Sébastien Gioria, Consultant Sénior en Sécurité des Systèmes d'Informations, Advens