Cogilog, faille de sécurité et erreurs à ne pas faire

À l’origine de cet article, je comptais me servir de Cogilog comme exemple typique d’un logiciel mal conçu. En effet, ce logiciel, très bien en terme fonctionnel, est un cas d’école de mauvais choix de conception.

Le problème, c’est que le cas d’école va très loin, on arrive au final à un prendre une invite de commande potentiellement en administrateur (et donc en root si on est malin).

Cet article présentera donc, comme je le souhaitais à la base, une analyse d’un point de vue attaquant du logiciel, de ses erreurs de conception ainsi que des recommandations pour les différents développeurs lisant mon blog pour éviter ce genre d’idiotie (car c’est bien de la bêtise à ce niveau).

De plus, cet article contiendra également des recommandations pour tous les utilisateurs de Cogilog pour boucher les trous le temps que l’éditeur daigne faire quelque chose.

À ce propos, j’ai contacté Cogilog le 18 novembre. Sans briser le secret des correspondances privées, les réponses apportées ne sont que des contournements non mis en avant dans la documentation et des fausses excuses semblant justifier le fait que, pour eux, l’urgence n’était pas là.

Dès maintenant, l’article que vous allez lire a été écrit au fil de mon analyse, donc sans savoir ce que j’allais trouvé ni quoi chercher en particulier.

Le déroulement à venir est donc quelque chose que vous pouvez utiliser comme base si vous cherchez à faire ce genre de chose par vous même.

La première chose à faire lorsque l’on installe et lance un logiciel, c’est se demander comment fonctionne-t-il. Surtout lorsqu’il travaille en mode client-serveur. Le risque d’un manque de sécurité sur la partie réseau est très grand.

Pour cela, un bon point de départ est de regarder la liste des processus lancés lorsque l’application est active.

Services lancés

Liste des processus lancés sur le serveur :

yoanngini@office ~ 10:59 % ps aux | grep -i cogilog
ladmin          44699   0,0  0,1  2564824   5812   ??  S    Dim03     0:00.74 /.cogilog/COGILOG Backup.app/Contents/MacOS/COGILOG Backup
ladmin            479   0,0  0,0  2460572    976   ??  S    17oct14   0:00.07 /.cogilog/COGILOGDetection
ladmin            374   0,0  0,0  2506216    496   ??  S    17oct14   0:04.08 /.cogilog/bin/postgres -D /.cogilog/data
yoanngini       87246   0,0  0,0  2423368    188 s000  R+   10:59     0:00.00 grep -i cogilog

Liste des processus lancés sur le client :

yoanngini@imac ~ 10:58 % ps aux | grep -i cogilog
yoanngini         620   0,0  0,1  2585948  12720   ??  S    30oct14   0:00.95 /.cogilog/COGILOG Backup.app/Contents/MacOS/COGILOG Backup
yoanngini         551   0,0  0,0  2506204   2616   ??  S    30oct14   0:01.29 /.cogilog/bin/postgres -D /.cogilog/data
yoanngini       47393   0,0  0,0  2432772    676 s000  S+   10:58     0:00.00 grep -i cogilog
yoanngini       47384   0,0  0,2  2682244  36644   ??  S    10:58     0:00.42 /Applications/COGILOG/COGILOG Gestion/COGILOG Gestion.app/Contents/MacOS/COGILOG Gestion

Première question : pourquoi cette base de données est stockée dans /.cogilog et tourne avec mon compte administrateur ?
Seconde question : pourquoi ai-je un service PostgreSQL qui écoute sur mon client alors que je travaille sur mon serveur uniquement ?

Emplacement des données

Première chose choquante ici : les données du service serveur sont stockées à la racine du système de fichier, dans un dossier caché .cogilog et tournant avec les droits de l’utilisateur ayant fait l’installation.

Un rapide coup d’œil sur les droits d’accès des éléments de ce dossier n’est pas pour nous rassurer :

yoanngini@office ~ 10:59 % ls -l /.cogilog
total 136
drwxr-xr-x   3 ladmin  staff   102B 31 mar  2014 COGILOG Backup.app/
drwxr-xr-x   3 ladmin  staff   102B 31 mar  2014 COGILOG Startup.app/
-rwxr-xr-x   1 ladmin  staff    61K 31 mar  2014 COGILOGDetection
drwxr-xr-x  23 ladmin  staff   782B 30 mar  2012 bin/
drwx------  19 ladmin  staff   646B 17 oct 19:29 data/
drwxr-xr-x  21 ladmin  staff   714B 31 mar  2012 lib/
drwxr-xr-x   3 ladmin  staff   102B  9 jul  2011 share/
-rwxr-xr-x   1 ladmin  staff   278B 30 mar  2012 version.plist

Le service « serveur » de Cogilog est donc installé à un emplacement non standard du système et tourne avec le compte ayant permis l’installation, donc un compte administrateur.

Aucun logiciel n’est censé travailler dans un dossier à la racine sous Mac. Si le logiciel est un service partagé, ses données vont dans /Library. Si les données sont propres à un utilisateur, cela va dans ~/Library. C’est ici la première erreur de Cogilog.

Cela veut dire que si ce compte est supprimé, Cogilog ne marche plus. Et pour mettre à jour Cogilog, il faut être authentifié avec cet utilisateur…

Cela veut également dire que si ce logiciel est compromis, l’attaque obtient une situation privilégiée sur le compte administrateur.

Base de données

Bien que mon instance de Cogilog soit en mode client-serveur, il installe une base de données sur les postes clients qui, comme pour le poste serveur, écoutent directement sur le réseau.

Si le service PostgreSQL est vulnérable à une attaque, elle exposera ainsi tout le parc, y compris les postes clients qui n’ont techniquement pas besoin du serveur de base de donnée.

La recommandation de l’éditeur vis-à-vis de ce vecteur d’attaque est de désactiver, via l’utilitaire Cogilog, le service serveur sur vos postes clients si vous n’utilisez pas la fonction de mise hors ligne des dossiers comptable.

Au vu des éléments à venir, vous devez impérativement désactiver ce service sur tous vos postes mobiles.

Sockets d’écoute du serveur :

yoanngini@office ~ 11:17 % sudo lsof -Pnp 374
Password:
COMMAND  PID   USER   FD    TYPE             DEVICE   SIZE/OFF      NODE NAME
...
postgres 374 ladmin    3u  systm                           0t0           
postgres 374 ladmin    4u   IPv6 0x72987f240c17ed99        0t0       TCP *:64998 (LISTEN)
postgres 374 ladmin    5u   IPv4 0x72987f240e2c1e39        0t0       TCP *:64998 (LISTEN)
postgres 374 ladmin    6u   unix 0x72987f240d1b60d1        0t0           /tmp/.s.PGSQL.64998
postgres 374 ladmin    7u   IPv6 0x72987f2406d4d5f1        0t0       UDP [::1]:62064->[::1]:62064
postgres 374 ladmin    8u   unix 0x72987f240d1b6009        0t0           ->0x72987f240d1b5e79

Sockets d’écoute du client :

yoanngini@imac ~ 11:35 % sudo lsof -Pnp 551     
COMMAND  PID      USER   FD    TYPE             DEVICE  SIZE/OFF     NODE NAME
...
postgres 551 yoanngini    4u   IPv4 0xb9da6be6f7f23231       0t0      TCP *:64998 (LISTEN)
postgres 551 yoanngini    5u   IPv6 0xb9da6be6e95c4ac1       0t0      TCP *:64998 (LISTEN)
postgres 551 yoanngini    6u   unix 0xb9da6be6f5b5a361       0t0          /tmp/.s.PGSQL.64998
postgres 551 yoanngini    7u   IPv6 0xb9da6be6ea6fa561       0t0      UDP [::1]:63478->[::1]:63478
postgres 551 yoanngini    8u   unix 0xb9da6be6f1b18619       0t0          ->(none)

Que ce soit sur le client ou le serveur, la base est en écoute directe sur le réseau. Et lorsqu’un client est lancé, une connexion est effectivement établie vers le serveur PostgreSQL.

yoanngini@imac ~ 11:38 % netstat -an | grep EST | grep 64998
tcp4       0      0  192.168.62.10.53192    192.168.42.10.64998    ESTABLISHED

La première chose qui doit venir à l’esprit en voyant ce genre de chose est très simple : est-ce que la communication est chiffrée ? Si oui, avec quel certificat ?

Pour chiffrer ses communications, PostgreSQL dispose d’une option ssl. Une rapide recherche à travers les fichiers de configuration de la base nous permettra de savoir si l’option est active.

root@office ~ 11:44 % find /.cogilog/data -type f -exec grep -H ssl {} \; 
/.cogilog/data/pg_hba.conf:# hostssl    DATABASE  USER  CIDR-ADDRESS  METHOD  [OPTIONS]
/.cogilog/data/pg_hba.conf:# hostnossl  DATABASE  USER  CIDR-ADDRESS  METHOD  [OPTIONS]
/.cogilog/data/pg_hba.conf:# "hostssl" is an SSL-encrypted TCP/IP socket, and "hostnossl" is a
/.cogilog/data/postgresql.conf:#ssl = off				# (change requires restart)
/.cogilog/data/postgresql.conf:#ssl_ciphers = 'ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH'	# allowed SSL ciphers
/.cogilog/data/postgresql.conf:#ssl_renegotiation_limit = 512MB	# amount of data between renegotiations

La version de PostgreSQL utilisé supporte bien le SSL, mais l’option n’est pas activée par Cogilog. Les communications réseau sont donc à priori en clair sur le réseau.

Cela pose deux problèmes :

  • le contenu échangé n’est pas chiffré, un attaquant avec une position privilégiée sur le réseau pourra accéder aux communications en clair
  • le contenu échangé n’est pas authentifié, un attaquant avec une position privilégiée sur le réseau pourra falsifier vos données

Le premier point est le plus grave, car Cogilog authentifie l’utilisateur. Le risque est donc grand de voir passer un mot de passe dans les communications réseau.

Analyse de trame réseau

Le plus simple pour savoir ce qui se passe entre un client et son serveur, c’est d’utiliser un système d’écoute de communication. Vous pouvez en apprendre plus sur le sujet en regardant la présentation que j’ai donnée à ce sujet lors de la conférence MacAdmins 2014 à l’université de Pennsylvanie.

Si vous ne maitrisez pas Wireshark, le plus simple ici est encore un tcpdump qui affiche directement le contenu de la communication sous forme ASCII. Après tout, on s’attend à trouver des requêtes SQL qui se baladent sur le réseau…

yoanngini@imac ~ 12:00 % sudo tcpdump -X port 64998

Lancez le tcpdump, ouvrez Cogilog et authentifiez-vous si vous n’avez pas enregistré le mot de passe puis admirez vos données de gestion s’étendre en clair sous vos yeux…

Faites un ctrl-C quand vous en avez assez et amusez-vous à rechercher votre mot de passe dans la sortie du terminal. Vous devriez trouver quelque chose de la sorte :

..hGq..M...d..E.
..&$@.@.....>...
*..g.....\..s...
...b............
..Q....select id
, nom, nomcourt,
 niveauAdmin, le
ngth(motDePasse)
, statut from ut
ilisateur where 
detruit=FALSE an
d lower(motDePas
se)=lower('LoL42
') and ((lower(n
omCourt)=lower('
yoann')) or (low
er(nom)=lower('y
oann'))).

Comme vous pouvez le voir… Mon mot de passe est LoL42 et le nom d’utilisateur associé est yoann.

En plus de nous apprendre que Cogilog stocke et transmet notre mot de passe utilisateur en clair, le laissant ainsi accessible au premier venu, cette ligne nous apprend d’autres choses tout aussi graves.

Remise en forme pour améliorer la lisibilité, la requête SQL est la suivante :

SELECT id, nom, nomcourt, niveauAdmin, LENGTH(motDePasse), statut FROM utilisateur WHERE detruit=FALSE AND LOWER(motDePasse)=LOWER('LoL42') AND ((LOWER(nomCourt)=LOWER('yoann')) OR (LOWER(nom)=LOWER('yoann')))

Le mot de passe transite en clair et est transformé à l’authentification pour ne contenir que des minuscules. Faites l’essai, authentifiez vous avec LoL42, lol42 ou LOL42, tout fonctionne…

Nouvelle erreur, très grave cette fois-ci, de Cogilog, les données d’authentification des clients sont accessibles en clair et la robustesse du mot de passe est diminuée sans aucune raison.

Tout protocole réseau se doit aujourd’hui d’utiliser du SSL pour chiffrer les communications, cela ne coute plus rien. Et dans le cas d’application métier, il est impératif d’utiliser du « Public Key Pinning« .

Le fait que le mot de passe soit visible ici pose une nouvelle question : comment le client est-il authentifié sur le serveur ? Est-ce que n’importe qui sur le réseau est capable de faire cette requête SQL ?

Protection du service PostgreSQL

Comment le service PostgreSQL est-il sécurisé ? La réponse se trouve dans les premiers paquets réseau capturés et dans les fichiers de configuration de la base Cogilog. Une authentification de type md5 est effectuée au début, comme demandé par la configuration du serveur.

root@office /.cogilog/data 12:14 % grep host /.cogilog/data/pg_hba.conf
host    all             all             127.0.0.1/32            md5
host    all             all             0.0.0.0/0               md5
host    all             all             ::1/128                 md5

Deux options possibles, les comptes des utilisateurs sont également utilisés pour authentifié la session SQL, ou alors un compte en dur est configuré.

Pour cela, essayons de nous authentifier directement avec le compte utilisateur volé durant l’écoute réseau :

yoanngini@imac ~ 12:22 % /.cogilog/bin/psql -h 192.168.42.10 -p 64998 -U yoann -W
Password for user yoann: 
psql: FATAL:  password authentication failed for user "yoann"

Pas de chance, ça ne marche pas. Regardons donc le code de Cogilog avec un outil de reverse engineering pour comprendre comment il s’authentifie. Pour cela j’utilise l’excellent Hopper Disassembler.

Avec un peu d’expérience, on fini par tomber sur la ligne qui nous intéresse :

Code d'authentification SQL du

Voici donc les identifiants et mots de passe utilisés dans l’intégralité des installations Cogilog…

Faisons un petit essai en ligne de commande :

yoanngini@imac ~ 12:49 % /.cogilog/bin/psql -h 192.168.42.10 -p 64998 -U jbo -W serveur
Password for user jbo: 
psql (9.0.4)
Type "help" for help.
 
serveur=#

C’est réussi, nous sommes connectés au serveur PostgreSQL, quelques commandes permettent de vérifier que l’on a bien accès à l’intégralité des informations en base, comme la liste des utilisateurs et leurs mots de passe, en clair.

serveur=# SELECT * FROM utilisateur;
 id |      nom      | nomcourt  | motdepasse | niveauadmin | niveaugestioncabinet | niveautempscabinet |    dateconnexion    | detruit | civ | tel | fax | email | statut | libre1 | libre2 | libre3 | libre4 | libre5 | libre6 | libre7 | libre8 | libre9 | libre10 
----+---------------+-----------+------------+-------------+----------------------+--------------------+---------------------+---------+-----+-----+-----+-------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+---------
  1 | Yoann Gini    | yoann     | LoL42      |           1 |                    0 |                  0 | 17/11/2014 11:55:12 | f       |   1 |     |     |       |      0 |      1 |      0 |      0 |      0 |      0 |        |        |        |        | 
  2 | DUPONT Pierre | DP        |            |           0 |                    0 |                  0 |                     | t       |   1 |     |     |       |      0 |      0 |      0 |      0 |      0 |      0 |        |        |        |        | 
  3 | à définir     | à définir |            |           0 |                    0 |                  0 |                     | t       |   1 |     |     |       |      0 |      0 |      0 |      0 |      0 |      0 |        |        |        |        | 
(3 ROWS)

Troisième erreur, encore plus grave, de Cogilog. Ce que je viens de mettre en démonstration ici permet d’accéder aux données de compatibilité et de gestion de l’ensemble des clients utilisant Cogilog et des clients des cabinets comptable utilisant Cogilog.

Cette faille se voit comme le nez au milieu de la figure. C’est réellement un cas d’école, et même s’il semblait que je sois le premier à la mettre en lumière sur un article de blog, une personne mal intentionnée n’aurait aucune difficulté à la trouver.

Cette situation est anormale. La logique même aurait été d’utiliser le système d’authentification natif de PostgreSQL pour gérer les utilisateurs. Les droits d’accès pouvant être conservés dans une table dédiée comme ici, mais sans les mots de passe. Ou mieux, les ACL et les vues PostgreSQL pourraient être utilisés pour garantir la sécurité des accès.

Vous le comprenez donc, Cogilog est troué de part en part, expose vos données et mots de passe directement sur Internet.

Pour ceux qui pensent que le risque est limité, car restreint au réseau local, rappelez-vous que la documentation de l’éditeur pour les accès distants est de faire une simple redirection de port avec le NAT de votre routeur.

Cela veut dire que tous les clients de Cogilog ayant suivi leurs recommandations pour l’accès distant sont exposés à des risques forts d’espionnage industriel…

Lorsque j’en ai parlé à l’éditeur, ils m’ont recommandé d’utiliser un VPN, chose que j’impose depuis toujours à mes clients utilisant Cogilog, et que, pour la petite histoire, je galère toujours à faire appliquer dans la demande de licence Cogilog :-)

Il est donc impératif que l’ensemble des installations de Cogilog soit vérifié. N’utilisez pas d’accès distant direct.

Cerise sur le gâteau…

Cerise sur le gâteau s’il en est, cette faille de sécurité permet, en se connectant sur les services PostgreSQL ouverts en grand sur Internet, tournant avec les droits du compte administrateur du serveur, de lire la majorité des fichiers sur le disque du serveur et d’en écrire également.

Il est techniquement possible de voler les données autres que comptable des clients de cogilog, il est même possible de se servir de Cogilog pour installer des backdoor.

Pour des raisons de sécurité, je ne vais pas expliquer ici comment faire cette partie. Mais une base PostgreSQL sans restriction de droit comme ici a les capacités de lire et écrire des fichiers aux emplacements accessibles sur les volumes locaux. Cogilog étant généralement installé en administrateur, et les comptes administrateur ayant généralement accès à toutes les données des TPE et PME, le risque est grand.

De même, cette même capacité de lecture et d’écriture peut permettre, lorsque l’on connait bien OS X d’obtenir une invite de commande sur n’importe quelle machine, et toujours si on connait bien OS X, de passer cet accès distant en root.

Laisser un commentaire