À 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 :
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.