Prédisons le futur

Mickael Jeanroy
May 29, 2019 · 4 min read
Image for post
Image for post

Chez malt, nous travaillons sur une stack côté back mélangeant plusieurs outils, avec entre autres une application backend en Java / SpringBoot et une base MongoDB (mais pour plus de détails sur les autres composants de la stack, n’hésitez pas à venir nous voir !).

Comme dans tout site public, nous avons eu à générer des tokens uniques et aléatoires pour certaines opérations sensibles.

Laissez moi vous raconter une histoire.

Use Case

L’idée ici est de générer un token aléatoire et unique pour une action utilisateur, token devant expirer dans le temps (environ 1 heure de durée de vie). Le développement de la feature s’est fait naturellement en utilisant les outils offerts à notre disposition (dans ce genre de cas, n’essayez pas de recoder ce genre de choses à la main, c’est un cas d’utilisation courant sans doute déjà résolu par des personnes très compétentes).

Nous sommes donc partis sur les fonctionnalités offertes par MongoDB, à savoir :

  • Les tokens utilisateurs sont stockés dans MongoDB.

Malheureusement, l’une de ces trois options a été un échec cuisant.

Ne laissons pas le suspense durer trop longtemps : un ObjectId utilisé comme token est une grossière erreur. Pour bien comprendre la raison, il nous faut rentrer un peu plus dans les détails de génération d’un ObjectId par MongoDB.

Qu’est-ce qu’un ObjectId ?

Pour répondre à cette question, reprenons la documentation de MongoDB que vous pouvez retrouver ici :

  • Un ObjectId est un identifiant unique sur 12 octets.

A priori, en lisant ça, on se dit qu’on est tranquille, non ?

Et bien pas tout à fait.

Commençons par le plus simple : la troisième partie d’un ObjectId est donc un incrément commençant par une valeur aléatoire. Conséquence : si on génère plusieurs ObjectId d’affilée, on obtient une partie qui n’est plus vraiment aléatoire et on peut facilement prédire le prochain incrément.

Concernant la deuxième partie “aléatoire”, l’implémentation a changé depuis MongoDB 3.4.

Avec MongoDB 3.2, cette deuxième partie n’est en fait pas aléatoire et est liée à la machine sur laquelle l’ObjectId sera généré car, en réalité, il peut être décomposé de la façon suivante :

  • Trois octets correspondant à un identifiant de la machine.

Notre bien-aimé CTO Hugo l’explique aussi très bien dans un article ici.

A partir de MongoDB 3.4, cette partie devient “vraiment” aléatoire, mais, en fait, pas tant que ça.

  • Si on regarde l’implémentation java : ces deux variables sont générées en static et ne changent ensuite plus (vous pouvez trouver les sources ici).
ObjectId("5cee4a91686504144c8ff6be")
ObjectId("5cee4a91686504144c8ff6bf")
ObjectId("5cee4a91686504144c8ff6c0")
ObjectId("5cee4a91686504144c8ff6c1")

On voit donc que l’on n’a pas vraiment d’aléatoire ici…

Ok, mais comment on peut l’exploiter ?

L’exploitation de ce type d’aléatoire est théoriquement la suivante : si on arrive à générer plusieurs ObjectId d’affilée dans la même seconde, on peut donc prédire la suite qui va être générée à partir du premier ObjectId !

Hmmm…

Essayons d’exploiter ça au travers de la fonctionnalité suivante.

Supposons un formulaire de reset de mot de passe :

  1. Lorsque je demande un reset de mon mot de passe, je reçois un mail me donnant le token (i.e l’ObjectId).

L’information importante ici est que je connais un “token” (celui qui m’est envoyé par mail), mais avec les faits énoncés au début, je peux donc prédire les tokens suivants !

Pour générer cette suite de token, une simple fonction javascript fera l’affaire (que je pourrai utiliser depuis les devtools de mon navigateur préféré) :

// Emails que je connais
var emails = [
'mickael.jeanroy+test1@gmail.com',
'mickael.jeanroy+test2@gmail.com',
'mickael.jeanroy+test3@gmail.com',
'mickael.jeanroy+test4@gmail.com',
'mickael.jeanroy+test5@gmail.com',
'mickael.jeanroy+test6@gmail.com',
'mickael.jeanroy+test7@gmail.com',
'mickael.jeanroy+test8@gmail.com',
'mickael.jeanroy+test9@gmail.com',
];
var queries = emails.map((x) => () => (
$.ajax({
url: '/url/reset/password',
type: 'POST',
contentType: 'application/x-www-form-urlencoded',
data: 'email=${x}',
}
));
function hack() {
queries.forEach((query) => {
query();
});
}

Avec cette fonction javascript, je vais appeler 9 fois mon URL de reset de mot de passe à la suite (i.e dans la même seconde). Dans la liste des emails connus, le premier mail m’appartient, mais pas forcément les suivants. :)

A partir du premier email reçu, je peux dès lors prédire les tokens suivants et prendre la main sur n’importe quel compte correspondant à la liste d’email que j’ai donné en paramètre. :)

Conclusion

Evidemment, le souci n’est pas sur l’ObjectId en tant que tel mais plutôt sur ce que signifie “aléatoire”. Vous l’aurez compris : un ObjectId ne doit jamais être utilisé en tant que token !

En java, vous pouvez par exemple utiliser UUID.randomUUID() qui utilise un SecureRandomgénérant une partie vraiment aléatoire (et si un UUID ne fait pas l’affaire pour vous, n’utilisez surtout pas un Randompseudo aléatoire).

Et j’oubliais : contrairement aux croyances, non un timestamp n’est pas aléatoire !

nerds-malt

Tout ce qui se passe sous le capot par l’équipe R&D de Malt…

Mickael Jeanroy

Written by

nerds-malt

Tout ce qui se passe sous le capot par l’équipe R&D de Malt (ex Hopwork). Vous pouvez retrouver nos projets Open Source sur Github. Ici nous parlerons de ce que nous faisons.

Mickael Jeanroy

Written by

nerds-malt

Tout ce qui se passe sous le capot par l’équipe R&D de Malt (ex Hopwork). Vous pouvez retrouver nos projets Open Source sur Github. Ici nous parlerons de ce que nous faisons.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store