All your password are belong to us

Ou pourquoi un mot de passe se doit d’avoir une taille minimum et d’éviter de suivre un motif particulier.

Comme vous le savez certainement, il n’est pas toujours aisé, pour un hacker ayant découvert une faille de sécurité dans un système informatique, de contacter les responsables du système pour le leur signaler. Certaines entreprises, la première me venant à l’esprit étant Google, prennent facilement en compte les alertes (il me semble). Dans d’autres cas, le simple fait de signaler la faille vous assimile à un pirate…

Sur cette petite introduction, je vais maintenant revenir sur une situation à laquelle j’ai été confronté. Pas de faille de sécurité à proprement parler, mais un constat sur la politique de création et d’utilisation des mots de passe d’une plate-forme. Penchons-nous donc sur le problème.

La plate-forme en question n’héberge pas de données personnelles sensibles à proprement parler, pas de numéro de carte de crédit, pas d’adresse. Elle propose néanmoins un compte pour l’utilisateur qui contient donc un minimum d’informations le concernant: ses noms et prénoms notamment. Venons en au sujet qui fâche: le mot de passe d’accès à l’espace utilisateur. Premièrement, il n’est pas possible de le modifier via l’interface. Deuxièmement, et c’est là que certains des lecteurs vont commencer à s’étrangler. Le mot de passe n’est composé que de 5 caractères.

Regardons le nombre de mot de passe différents qu’il est possible d’obtenir si on considère que le mot de passe est généré avec au choix:
* les lettres de l’alphabet latin en minuscule ou en majuscule (52 possibilités).
* les chiffres (10 possibilités).
* les caractères spéciaux comme @ / [ {  (34 possibilités environ).
En résumé, pour chaque caractère: 52 + 10 + 34 = 96 possibilités.
Ce qui nous donne donc 96 *96 *96 *96 *96 = 96^5 = 8 153 726 976, un peu plus de 8 milliard de mot de passe différents. C’est peu mais ça reste beaucoup plus élevé que le nombre qui va suivre.

En effet, le massacre n’est pas fini… les mots de passe suivent un motif particulier. Ils sont constitués de l’année de naissance de l’utilisateur avec la première lettre de son nom. En résumé, une lettre et un nombre à 4 chiffres, 10 * 10 * 10 * 10 * 26 soit 260 000 possibilités. Nous sommes loin des 8 milliard initiaux et ça commence à faire peur.

Ce nombre peut encore être réduit, puisqu’on parle d’année de naissance. En considérant un intervalle simple d’une centaine d’année, on est presque sûr d’englober tous les cas possible, à l’exception des personnes plus que centenaire. En définitive, on se retrouve avec grand maximum 100 * 26 = 2 600 mots de passe différents ce qui est tout simplement ridicule.

Alors évidemment, on pourrait me faire remarquer qu’il faudrait connaître l’identifiant du compte pour que cela deviennent dangereux. Effectivement, et dans notre cas, oh malheur! L’identifiant est un nombre à 9 chiffres. A mon sens, la sécurité est insuffisante sur cette plate-forme, à cause d’une politique de génération des mots de passe défectueuse.

Imaginons un instant que l’on puisse tester un mot de passe par seconde… Vous voyez où je veux en venir n’est-ce pas? En moins d’une heure, toutes les possibilités pourraient être essayer. Cela prendrait seulement 43 minutes et 20 secondes pour être précis.

Si j’écris tout cela, c’est pour tenter de montrer combien un mot de passe qui suit un même motif pour tous les utilisateurs est un mauvaise chose et encore plus lorsque la génération se base sur des données personnelles comme la date de naissance. Je peux comprendre une partie des choix qui ont pu mener à une telle situation: facilité de mémorisation pour les utilisateurs, inutilité d’un système de récupération de mot de passe puisque le motif est connu de tous, place réduite en base de donnée. Quand bien même, tous ces arguments me semblent insuffisants pour justifier une telle politique de sécurité, d’autant plus lorsqu’on manipule un minimum de données personnelles.

Bloc-note dans le navigateur

Une petite astuce trouvé sur le net pour disposer d’un bloc-note dans le navigateur en utilisant simplement du html. Il suffit d’utiliser le code suivant dans la barre d’adresse du navigateur:

data:text/html, <html contenteditable>

Un petit marque-page et le tour est joué.

J’utilise personnellement une version légèrement personnalisée avec texte blanc sur fond noir:

data:text/html, <html contenteditable style="color:white; background-color:black">

Parsing mongostat data with Logstash

On my way to a complete MongoDB monitoring solution, I’ve been playing with mongostat to see what I can achieve with it. So I tested mongostat on a simple architecture made of two shards, each shard being a replica set composed of tree members. Of course, we also have tree configuration routers and one query router.

First, I discovered a few bugs in the tool when using it with the option discover. This parameter can be used to automatically retrieve statistics from all members of a replica set or a sharded cluster. Using it with version 2.4.9 of mongostat causes some other parameters to be ignored: rowcount and noheaders. So I dove in the code on Github to find that this bugs had been already corrected. We just need modifications to come to the stable version.

mongostat --host localhost:24000 --discover --noheaders -n 2 30 > mongostat.log

Here I’m connecting to my query router, asking mongostat to find other mongoDB instances by itself. Options noheaders and n don’t work at the moment but that’s not a problem. With this setup, I will receive logs every 30s.

There are two types of log: the ones coming from the mongos and the ones coming from the other.

localhost:21000        *0     *0     *0     *0       0     1|0       0   800m  1.04g    30m      0 local:0.0%          0       0|0     0|0   198b   924b    13 rs0  PRI   15:36:55
 localhost:21001        *0     *0     *0     *0       0     1|0       0   800m  1.01g    29m      0  test:0.0%          0       0|0     0|0   138b   359b     6 rs0  SEC   15:36:55
 localhost:21002        *0     *0     *0     *0       0     1|0       0   800m  1.01g    29m      0  test:0.0%          0       0|0     0|0   138b   359b     6 rs0  SEC   15:36:55
 localhost:21100        *0     *0     *0     *0       0     1|0       0   800m  1.05g    35m      0 local:0.0%          0       0|0     0|0   198b   924b    13 rs1  PRI   15:36:55
 localhost:21101        *0     *0     *0     *0       0     1|0       0   800m  1.01g    34m      0  test:0.0%          0       0|0     0|0   138b   359b     6 rs1  SEC   15:36:55
 localhost:21102        *0     *0     *0     *0       0     1|0       0   800m  1.01g    34m      0  test:0.0%          0       0|0     0|0   138b   359b     6 rs1  SEC   15:36:55
 localhost:24000         0      0      0      0       0       0                  174m     5m      0                                             2b    23b     2      RTR   15:36:55

Output from mongostat.

 

The last line coming from the mongos has empty fields. So we will need to deal with it when parsing the log. After having understood how mongostat works, it is now time to see if we can easily plug it in logstash. Let’s take a look at our logstash configuration file.

input {
  file {
    type => "mongostat"
    path => ["/path/to/mongostat.log"]
  }
}

We define where to find the log file.

 

filter {
  if [type] == "mongostat" {
    grok {
      patterns_dir => "./patterns"
      match => ["message", "%{HOSTNAME:host}:%{INT:port}%{SPACE}%{METRIC:insert}%{SPACE}%{METRIC:query}%{SPACE}%{METRIC:update}%{SPACE}%{METRIC:delete}%{SPACE}%{METRIC:getmore}%{SPACE}%{COMMAND:command}%{MONGOTYPE1}%{SIZE:vsize}%{SPACE}%{SIZE:res}%{SPACE}%{NUMBER:fault}%{MONGOTYPE2}%{SIZE:netIn}%{SPACE}%{SIZE:netOut}%{SPACE}%{NUMBER:connections}%{SPACE}%{USERNAME:replicaset}%{SPACE}%{WORD:replicaMember}%{SPACE}%{TIME:time}"]
    }
  }
  if [tags] == "_grokparsefailure" {
    drop { }
  }
  if [message] == "" {
    drop { }
  }
}

We apply filter on each message received if it comes from mongostat. If the message is empty or grok fails to parse it, we drop the log.

 

output {
  stdout { }
  elasticsearch_http {
    host => "127.0.0.1"
  }
}

Simple output. Logs are stored in Elasticsearch so that we can use Kibana to examine them later and are written to stdout for immediate debugging and verification.

Let’s consider a little bit the filter part. We give grok a directory for our personal patterns: ./patterns. This directory contains a file mongostat with the following patterns:

METRIC (\*%{NUMBER})|(%{NUMBER})
COMMAND (%{NUMBER}\|%{NUMBER})|(%{NUMBER})
SIZE (%{NUMBER}[a-z])|(%{NUMBER})
LOCKEDDB (%{WORD}\:%{NUMBER}%)
MONGOTYPE2 (%{SPACE}%{LOCKEDDB:lockedDb}%{SPACE}%{NUMBER:indexMissedPercent}%{SPACE}%{COMMAND:QrQw}%{SPACE}%{COMMAND:ArAw}%{SPACE})|%{SPACE}
MONGOTYPE1 (%{SPACE}%{NUMBER:flushes}%{SPACE}%{SIZE:mapped}%{SPACE})|%{SPACE}

The MONGOTYPE patterns are used to deal with empty fields from mongos log line.
The rest of the match directive is just about capturing each field from mongostat to produce a more readable and analysable output.

To create this, I used an online Grok debugger which is very useful because you needn’t to reload logstash every time you want to test your work. It also provides you instant feedback.

I’m know waiting for the bugs to be fixed in stable so that this solution could be more useful to monitor mongo and maybe use it in production.

List of available patterns on Github.