Skip to main content
HDWSec
Pentest SécuPentestOWASPRGPDFuite de donnéesCyber · 9 min read

Chiffrement défaillant : quand un MacBook suffit à ouvrir la base des salaires

Le GPU d'un MacBook Pro M1 calcule 2,3 milliards d'empreintes SHA-1 par seconde. Retour d'audit chez un éditeur de paie français où les mots de passe, les RIB et les jetons de réinitialisation étaient protégés par du chiffrement qui ne protégeait rien.

TR
Thomas RABIET Ingénieur cybersécurité
Chiffrement défaillant : quand un MacBook suffit à ouvrir la base des salaires

Confidentialité. Pour des raisons évidentes de confidentialité, le nom du client, sa pile technique, ses volumes et les détails de la mission ont été modifiés, et ce que vous lisez en est une version romancée. Les défauts décrits, eux, sont ceux qu’on a trouvés.

Deux milliards d’essais par seconde

2 317 000 000. C’est le nombre d’empreintes SHA-1 que calcule en une seconde le processeur graphique d’un MacBook Pro M1. Pas une machine de calcul dédiée, pas une ferme de cartes graphiques louée à l’heure, juste un ordinateur portable récent.

Retenez ce chiffre, on y revient à la fin.

Le client : Paylio, un éditeur français de SIRH et de paie en mode SaaS, utilisé par des PME. Bulletins, notes de frais, arrêts de travail, coordonnées bancaires, numéros de sécurité sociale. Autrement dit, une base qui rassemble à peu près tout ce qu’il y a de privé dans la vie professionnelle d’une personne.

Le format de la mission : un pentest en boîte grise. On nous fournit un compte de démonstration sur la plateforme, comme n’importe quel client en essai, et rien d’autre. Pas de code, pas de schéma d’architecture, pas d’accès privilégié.

Ce qui suit relève de la catégorie A04:2025 Cryptographic Failures, quatrième du Top 10 OWASP dans l’édition 2025, en recul de deux places puisqu’elle occupait la deuxième position en 2021. Elle couvre l’absence de cryptographie, la cryptographie trop faible, et la fuite des clés.

Une chose est sûre : ce n’est pas parce qu’on chiffre des données qu’elles sont protégées. Chez Paylio, il y avait du chiffrement partout, et la suite de cet article montre ce que ça valait.

Le cas en bref

  • Secteur : éditeur français de SIRH et de paie en SaaS
  • Catégorie OWASP : A04:2025 Cryptographic Failures
  • Format : pentest en boîte grise, à partir d’un simple compte de démonstration
  • Défaut 1 : jetons de réinitialisation dérivés de l’horloge, donc prédictibles, visibles dans l’URL
  • Défaut 2 : clé de chiffrement des coordonnées bancaires écrite en dur dans le code source
  • Défaut 3 : mots de passe hachés en SHA-1, sans sel
  • Correctif : générateur cryptographique pour les jetons, clés sorties du code, fonction de hachage lente et salée

Pourquoi le sel change tout

Hacher un mot de passe, c’est le passer dans une moulinette à sens unique. On obtient une empreinte, une chaîne de longueur fixe, et aucune clé ne permet de revenir en arrière. MD5 et SHA-1 sont les plus connues de ces fonctions, et toutes deux sont sorties de l’état de l’art depuis longtemps. SHA-256 reste très bien pour vérifier qu’un fichier n’a pas été modifié, mais elle est bien trop rapide pour des mots de passe, où on cherche au contraire une fonction lente.

Le principe, lui, est le bon :

À l'inscription
    tirer un sel au hasard, différent pour chaque compte
    mot de passe saisi + sel  ->  moulinette  ->  empreinte
    stocker le sel ET l'empreinte

À la connexion
    relire le sel stocké pour ce compte
    mot de passe saisi + ce sel  ->  moulinette  ->  empreinte
    comparer à l'empreinte stockée

L’application n’a donc jamais besoin de connaître le mot de passe. Elle a seulement besoin de savoir si celui qu’on lui présente redonne la même empreinte.

Sans sel, en revanche, la moulinette rend toujours le même résultat pour la même entrée. Si trois cents salariés ont choisi « Bonjour2024 », les trois cents lignes de la base sont rigoureusement identiques, et il suffit de calculer cette empreinte une seule fois pour les repérer toutes. Le sel, c’est la pincée différente ajoutée à chaque compte avant le calcul, celle qui rend ces trois cents lignes toutes différentes.

Reste la question que tout le monde se pose : où est rangé ce sel ? Avec l’empreinte, dans la même colonne, et parfaitement lisible. Une empreinte bcrypt ressemble à ça :

$2b$10$AMkKp5AQoNv7Arovlg9bvuQ6C470TxMd5T9zznWVAIXlrcXnyPdMe

Tout y est : $2b$ pour la variante de l’algorithme, 10 pour le coût, puis 22 caractères de sel et 31 caractères d’empreinte. Ce n’est pas une négligence, le sel n’a jamais eu vocation à être secret. Son rôle est d’empêcher l’attaquant de mutualiser son travail, puisque sans lui un seul calcul teste toute la base, alors qu’avec lui il faut recommencer compte par compte. Le sel ne sauve donc pas un mot de passe faible, il garantit qu’il tombe seul au lieu d’entraîner avec lui les deux cent quatre-vingt-dix-neuf autres.

Trois défauts, dans l’ordre où on les trouve

Le jeton de réinitialisation est prédictible. C’est le premier, et on le voit sans rien compromettre du tout. On demande une réinitialisation de mot de passe depuis notre compte de démonstration, on regarde le lien reçu, et le jeton s’affiche en clair dans l’URL. On en demande plusieurs à la suite, et les valeurs se ressemblent beaucoup trop : elles avancent avec l’heure.

Un jeton dérivé de l’horloge n’est pas un secret, c’est une date déguisée. Qui connaît approximativement l’instant d’envoi peut reconstituer l’intervalle des valeurs possibles et le parcourir. Autrement dit, il suffit de déclencher une réinitialisation sur le compte de quelqu’un d’autre pour pouvoir la détourner.

La clé de chiffrement des RIB est dans le code source. À partir de là, l’audit progresse et on finit par obtenir un accès au système via une exécution de code à distance. Le détail de cette exploitation n’est pas le sujet de cet article, et il relèverait de toute façon d’une autre catégorie du Top 10. Retenez seulement qu’une faille en amène une autre, et que c’est précisément l’intérêt d’un pentest par rapport à un scanner qui teste des points isolés.

Une fois dedans, on lit le code de l’application. Les coordonnées bancaires y sont bien chiffrées, en AES-256, ce qui est un bon algorithme. Le souci : la clé est écrite dans le code, donc versionnée, donc présente dans l’historique Git, sur les postes de tous les développeurs passés par le projet, et dans chaque sauvegarde du dépôt.

Les mots de passe sont en SHA-1 sans sel. Et c’est en accédant à la base qu’on tombe dessus. SHA-1 n’a rien d’un mauvais algorithme en soi, il a même été conçu pour être rapide. C’est précisément le problème : la vitesse, qui est une qualité pour vérifier l’intégrité d’un fichier, devient un cadeau pour qui veut essayer des milliards de combinaisons.

Les trois défauts tiennent en quatre lignes dans le code :

// Ce qu'on trouve dans le code de Paylio
$jeton = uniqid();
const CLE_RIB = 'Pyl0_Secret_2019';
$rib = openssl_encrypt($iban, 'aes-256-cbc', self::CLE_RIB, 0, $iv);
$hash = sha1($motDePasse);

En PHP, uniqid() construit sa valeur à partir de l’heure courante en microsecondes, et la documentation officielle précise elle-même que la fonction ne convient pas pour produire un jeton de sécurité. C’est exactement ce qui rend la progression visible à l’œil nu dans l’URL.

L’essai, avec l’outil standard du métier

$ hashcat -m 100 -a 0 empreintes.txt mots.txt -r mangling.rule

Speed.#01........:  2317.2 MH/s (Apple M1 Pro)
Recovered........: 480/1038 Digests
Progress.........: 1456/1456 (100.00%)
Status...........: Exhausted

Le -m 100 désigne le mode SHA-1 : hashcat associe un numéro à chaque type d’empreinte, et on trouve la correspondance dans sa documentation.

Le fichier de règles, lui, applique automatiquement les transformations que les humains font tous. Il ressemble à ça, une règle par ligne :

:              le mot tel quel
c              Majuscule en tête
$!             ajoute un ! à la fin
$2$0$2$4       ajoute 2024 à la fin
c$2$0$2$4      Majuscule en tête, puis 2024
c$2$0$2$4$!    Majuscule, puis 2024, puis !

C’est exactement ce que fait un salarié à qui on impose une majuscule, un chiffre et un caractère spécial. La politique de mot de passe produit des variations, et l’outil connaît les variations.

56 mots de vocabulaire, 26 règles, 1456 essais au total. La barre de progression n’a pas eu le temps de s’afficher.

« Nos mots de passe sont chiffrés »

C’est la phrase qu’on entend en restitution, et elle est dite de bonne foi. Elle révèle pourtant le nœud du sujet, parce qu’un mot de passe ne doit justement pas être chiffré. Chiffrer, c’est réversible, et c’est bien ce qu’on veut pour un RIB puisqu’il faut pouvoir le relire. Pour un mot de passe, personne n’a de raison de le relire, pas même l’éditeur. Si l’application est capable de retrouver le mot de passe d’origine, alors quiconque met la main sur la clé le peut aussi, et on retombe exactement sur le problème des RIB de Paylio.

L’autre réponse fréquente, c’est « de toute façon nos données sont chiffrées au repos ». Le chiffrement au repos, c’est le fait de chiffrer le disque ou la base là où les données dorment, pour que quelqu’un qui repartirait avec le matériel ou avec une copie de sauvegarde ne puisse rien en lire. C’est utile, et c’est même devenu une case à cocher dans beaucoup de contrats.

Sauf que dès que l’application tourne, elle a besoin de lire ces données, donc elle les déchiffre. Une requête envoyée à l’application reçoit du texte en clair, qu’il y ait chiffrement au repos ou non. Ça protège du vol de disque, pas de l’attaquant qui parle à l’application. Et le second scénario est de très loin le plus fréquent.

C’est d’ailleurs la question à se poser devant n’importe quelle mesure de sécurité : contre qui protège-t-elle exactement ? Un chiffrement qui protège du vol de disque mais pas de l’attaquant qui parle à l’application ne couvre pas le scénario le plus probable.

Comment y remédier

Comme souvent, une partie se règle vite et une autre demande un vrai chantier.

  • Hacher avec une fonction conçue pour ça, et dans le bon ordre. L’OWASP recommande Argon2id en premier choix, scrypt ensuite, bcrypt uniquement pour les systèmes existants qui ne peuvent pas migrer, et PBKDF2 quand une conformité FIPS l’impose. Ces fonctions sont volontairement lentes et gourmandes en mémoire, et c’est exactement leur intérêt.
  • Sortir les clés du code. Un gestionnaire de secrets, ou au minimum des variables injectées au déploiement et jamais versionnées. Une clé écrite dans un dépôt est une clé publiée, et la retirer aujourd’hui ne l’efface pas de l’historique. Ce cas est explicitement couvert par la catégorie A04, sous la CWE-321 Use of Hard-coded Cryptographic Key, et la CWE-320 sur les erreurs de gestion de clés.
  • Générer les jetons avec un générateur cryptographique. Celui que le langage prévoit pour la sécurité, pas celui qui sert à tirer un nombre au hasard pour l’affichage.
  • Prévoir la rotation des clés. Une clé qui n’a pas changé depuis la première version du produit a eu le temps de fuiter plusieurs fois.
  • Forcer la réinitialisation après correction. C’est l’étape qu’on oublie presque toujours. Repasser la base en Argon2id ne sert à rien si les anciennes empreintes ont déjà été copiées, il faut invalider les mots de passe existants.

Et la règle qui tient en une phrase : en cryptographie, on n’invente rien et on n’assemble rien soi-même. On prend la fonction prévue pour l’usage, avec les paramètres recommandés, et c’est tout.

Ce que ça coûte

  • Article 32 du RGPD. Il impose des mesures de sécurité tenant compte de l’état de l’art. Un algorithme déclassé depuis vingt ans ne relève pas de l’appréciation, c’est un manquement caractérisé.
  • EDF, 600 000 euros, novembre 2022. Plus de 2,4 millions de mots de passe du compte client hachés sans sel, et du MD5 encore en service sur plus de 25 800 comptes jusqu’en juillet 2022 (communiqué CNIL, délibération SAN-2022-021).
  • La doctrine est publique et précise. La CNIL détaille ce qu’elle attend en matière de stockage des mots de passe, et son référentiel sur la gestion du personnel encadre spécifiquement les données RH. Personne ne peut plaider l’ignorance.
  • Le coût commercial dépasse l’amende. Pour un éditeur de paie, la sécurité est un argument de vente. Une fuite de bulletins et de RIB se paie au renouvellement des contrats, et dans chaque appel d’offres suivant.

Et il y a un effet particulier aux données RH. Un salarié dont le mot de passe professionnel tombe voit souvent tomber avec lui ses comptes personnels, parce que le même mot de passe sert ailleurs. La fuite ne s’arrête pas aux portes de l’entreprise.

Pourquoi cette faille survit

Le code fonctionne parfaitement. Un mot de passe haché en SHA-1 se vérifie très bien à la connexion. Aucun test ne tombe, aucun utilisateur ne se plaint, aucun indicateur ne bouge. Le défaut est invisible tant que personne ne regarde la base avec un œil d’attaquant.

La ligne a été écrite il y a dix ans. sha1() était un choix courant à l’époque. Le code a survécu aux migrations, aux refontes du front, aux changements d’équipe. Personne ne rouvre une fonction d’authentification qui marche.

Corriger coûte cher en apparence. On ne peut pas recalculer les empreintes sans les mots de passe en clair, qu’on n’a pas. Il faut donc soit rehacher au fil des connexions, soit forcer une réinitialisation générale, ce qui veut dire déranger tous les utilisateurs. C’est le genre d’arbitrage qui se reporte de trimestre en trimestre.

La cryptographie donne un faux sentiment d’achèvement. Voir « AES-256 » dans le code rassure immédiatement. On ne se demande plus où est la clé.

Comment savoir si vous êtes exposé

Trois questions à poser à votre équipe technique. Les réponses prennent cinq minutes, et elles suffisent à situer le niveau de risque :

  • Quelle fonction hache nos mots de passe, et depuis quelle année ?
  • Où sont nos clés de chiffrement, et est-ce qu’elles sont passées un jour par un dépôt Git ?
  • Avec quelle fonction sont générés nos jetons de réinitialisation et nos identifiants de session ?

Si la première réponse contient « SHA » ou « MD5 », le sujet est ouvert. Si la deuxième hésite, il l’est aussi.

Un scanner automatique ne vous aidera pas ici. Il voit une application qui répond correctement, avec un certificat valide, et n’a aucun moyen de savoir comment la base est remplie derrière. Ce défaut se trouve en atteignant les données, ce qui suppose un audit qui va au bout de la chaîne au lieu de s’arrêter au premier point testé.

Côté référentiels, l’ASVS de l’OWASP consacre un chapitre entier à la cryptographie et au stockage des secrets, la Password Storage Cheat Sheet donne les paramètres à jour, et l’ANSSI publie ses propres recommandations sur les mécanismes cryptographiques. C’est sur ces référentiels que s’appuie notre test d’intrusion web et API. Côté classification, ce qu’on a trouvé chez Paylio relève de la CWE-338 pour le générateur pseudo-aléatoire faible, de la CWE-321 pour la clé en dur et de la CWE-327 pour l’algorithme déclassé.

Conclusion

Il y avait du chiffrement partout dans le code de Paylio. AES-256 sur les données bancaires, du hachage sur les mots de passe, du TLS sur toutes les connexions. Sur le papier, la case était cochée.

C’est ce qui rend cette catégorie particulière. Elle ne vise pas ceux qui n’ont rien fait, elle vise ceux qui ont fait quelque chose sans vérifier contre quoi ça protégeait. Une clé rangée à côté du coffre, un algorithme choisi pour sa vitesse alors qu’on avait besoin de lenteur, un hasard qui n’en est pas un.

Si vous éditez un produit qui stocke des mots de passe, des coordonnées bancaires ou n’importe quel secret appartenant à vos utilisateurs, la question n’est pas de savoir si vous chiffrez. Vous chiffrez sûrement. Elle est de savoir contre qui, et avec quelle clé rangée où.

Et pendant qu’on en discute, un MacBook posé sur un bureau continue de calculer deux milliards d’essais par seconde.

→ Notre test d’intrusion web et API

→ Demandez un pentest

TR
Written by Thomas RABIET Ingénieur cybersécurité

Ready to test your security?

Our experts conduct penetration tests tailored to your scope and challenges, with a clear report and actionable recommendations.