Protection des mots de passe par Firefox et Thunderbird : analyse par la pratique

MISC n° 069 | septembre 2013 | Laurent Clévy
  • Actuellement 0 sur 5 étoiles
0
Merci d'avoir participé !
Vous avez déjà noté cette page, vous ne pouvez la noter qu'une fois !
Votre note a été changée, merci de votre participation !
Lorsque l'on demande à Firefox de mémoriser des mots de passe, il est connu que ceux-ci sont stockés dans le fichier signons.sqlite, chiffrés via 3DES avec une clé située dans key3.db [1]. Mais se posent alors plusieurs questions. Par exemple, comment cette clé est-elle dérivée du 'mot de passe principal' ? Une recherche par force brute est elle possible? Cet article propose justement de décrire les formats de données et algorithmes utilisés afin de protéger vos mots de passe dans Firefox et Thunderbird.

Le gestionnaire de mots de passe de Firefox

Le Password Manager [1] de Firefox permet de stocker les mots de passe et identifiants (login) associés à un site web. Il s'agit par exemple des valeurs saisies dans les champs userid et pass de la page de connexion https://signin.ebay.fr. Au moment où l'on retourne sur une page dont le mot de passe est sauvegardé, Firefox préremplit les valeurs de ces champs. On suppose que dans un formulaire HTTP POST, lorsque le nom d'un premier champ contient user ou login, suivi par un deuxième champ nommé pass ou password (et de type password), Firefox les identifie alors comme paire login/mot de passe.

Le même site officiel [1] explique que ces mots de passe sont stockés dans les fichiers key3.db et signons.sqlite du profil utilisateur Firefox. Il est également recommandé d'utiliser un Master Password afin de protéger ces mots de passe.

Structure et contenu de Signons.sqlite

Intéressons-nous d'abord à ce dernier fichier, au format SQL, et examinons la structure de la table SQL moz_logins. Pour l'exemple précédent, les valeurs des colonnes les plus intéressantes sont :

hostname=https://signin.ebay.fr

formSubmitURL= https://signin.ebay.fr

usernameField=userid

passwordField=pass

encryptedUsername=MDoEEPgAAAAAAAAAAAAAAA...

encryptedPassword=MDoEEPgAAAAAAAAAAAAAAA...

On comprend donc comment sont stockés les mots de passe associés à chaque page. La question suivante est bien sûr : comment sont chiffrés les champs username et password ?

Choisissons une paire utilisateur/mot de passe complet. Nous garderons d'ailleurs cet exemple jusqu'à la fin de l'article, y compris pour le contenu de key3.db,

user =MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECMRMLjokk1SaBAiS/0sPBIYdEQ==

passwd=MDoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECGt3JoezKgoUBBB4Jwij9kC+NmHDW3zfcnnA

On reconnaît facilement l'encodage Base64 grâce à la suite ...AAAAAA..., encodant des octets nuls. Après décodage, chaque séquence de 80 caractères Base64 donne 60 octets, ici représentée comme séquence hexadécimale.

user=30320410f8000000000000000000000000000001301406082a864886f70d03070408c44c2e3a2493549a040892ff4b0f04861d11

passwd=303a0410f8000000000000000000000000000001301406082a864886f70d030704086b772687b32a0a140410782708a3f640be3661c35b7cdf7279c0

Les 2 valeurs sont encodées en ASN1 DER [2]. La syntaxe est type-longueur-valeur. Ici, respectivement 0x30, 0x32, puis la séquence de 50 octets 0410...1d11. Le type 0x30 est une séquence (SEQUENCE), le type 4 une chaîne d'octet (OCTETSTRING), le type 6 « OBJECT IDENTIFIER », etc.

Après décodage, la chaîne user se lit comme suit (les codes couleur pour les types et les valeurs sont conservés) :

SEQUENCE {

OCTETSTRING f8000000000000000000000000000001

SEQUENCE {

OBJECTIDENTIFIER 1.2.840.113549.3.7 // DES-EDE3-CBC

OCTETSTRING c44c2e3a2493549a

}

OCTETSTRING 92ff4b0f04861d11

}

et la chaîne passwd

SEQUENCE {

OCTETSTRING f8000000000000000000000000000001

SEQUENCE {

OBJECTIDENTIFIER 1.2.840.113549.3.7 // DES-EDE3-CBC

OCTETSTRING 6b772687b32a0a14

}

OCTETSTRING 782708a3f640be3661c35b7cdf7279c0

}

Finalement, on obtient pour chaque enregistrement un triplet de 3 valeurs, dont la première f800....0001 est constante. L'Object Identifier (ou OID) signifie que l'algorithme de chiffrement utilisé [4] est 3DES EDE3 (avec 3 clés de 56 bits chacune) et en mode CBC, ce qui nécessite un vecteur d'initialisation (IV), qui pour 3DES est de 8 octets.

Justement, à chaque fois la deuxième chaîne d'octets (OCTETSTRING) fait 8 octets de long (peut être l'IV ?) et la deuxième chaîne possède une longueur multiple de 8, soit la taille du bloc de l'algorithme DES.

Nous y reviendrons plus tard, car il nous faut trouver la clé 3DES. Examinons donc le fichier key3.db.

Structure et contenu de key3.db

Cette base de données est de format Berkeley en version 1.85 [3].

$ file key3.db

key3.db : Berkeley DB 1.85 (Hash, version 2, native byte-order)

Note

Aperçu de la structure d'une base de données BSDDB v1.85 

Il existe un module python (bsddb185) et un utilitaire sous Linux (dump_db185), mais pourquoi ne pas jeter un œil à la structure de cette base ? Nous allons voir qu'il est facile d'accéder aux données.

0x000000: 00061561 00000002 000004d2 00001000

...

0x000030: 00000001 00010000 00000004 00000001

...

Le fichier hash.h dans le code source [3] défini l’entête de la base, dont voici les données nécessaires à la récupération du contenu.

La première valeur de 32 bits (0x61561) permet de reconnaître qu'il s'agit d'une base Berkeley DB de type Hash, la deuxième (offset 4) la version, ici la valeur 2 signifie « version 1.85 ». À l'offset 12, se trouve la taille des pages, unité de base des données chargées en mémoire, ici 0x1000 octets. Enfin, on peut apprendre que cette base contient 4 clés, valeur stockée à l'offset 0x38.

Au début de chaque page, après l’entête, est stockée une table de pointeurs (sur 16 bits, little endian) sur les données stockées dans la page, à partie de l'offset 2 :

0x001000: 0600f90f f80fea0f b60fab0f 970f850f

0x001010: 970fffff ffffffff ffffffff ffffffff

Ainsi, cette table indique 5 pointeurs vers 3 paires clé/valeurs : 0xff9, 0xff8, 0xfea, 0xfb6, 0xf97. En partant à rebours de la fin de la page, et par alternance cela permet de trouver chaque clé puis valeur des enregistrements Version, puis password-check et enfin global-salt.

0x001f90: ffffffff ffffff31 5aa1088a 0c1fba32 .......1Z......2

0x001fa0: cf613eec e884a02a 9ac0cd67 6c6f6261 .a>....*...globa

0x001fb0: 6c2d7361 6c740314 01ad6f10 16c8bf0b l-salt....o.....

0x001fc0: eeeeeee3 add1bf05 c275da42 fd000b2a .........u.B...*

0x001fd0: 864886f7 0d010c05 0103a989 f3015f50 .H............_P

0x001fe0: 87fecae9 8435ad09 fc737061 7373776f .....5...spasswo

0x001ff0: 72642d63 6865636b 03566572 73696f6e rd-check.Version

La deuxième page, toujours dans notre exemple, stocke l'enregistrement avec la clé f8000000000000000000000000000001, aux offsets 0xff0 et 0xf5d :

0x002000: 0200f00f 5d0f530f 5d0f0000 00000000 ....].S.].......

...

0x002f50: 00000000 00000000 00000000 00030001 ................

0x002f60: 0030818c 3028060b 2a864886 f70d010c .0..0(..*.H.....

...

0x002ff0: f8000000 00000000 00000000 00000001

Dans le script firepwd.py fourni à titre didactique avec cet article [7], la fonction readBsddb() lit le contenu de la base dans ce format et retourne un dictionnaire Python.

key3.db contient quatre enregistrements dont les identifiants sont f8000000000000000000000000000001, Version, password-check et global-salt. Le Dr Stephen Henson [5] et Plakosh and Al. [6] expliquent comment utiliser la valeur password-check que voici, quelque peu « mise en forme » pour en faciliter l’interprétation :

031401 ad6f1016c8bf0beeeeeee3add1bf05c275da42fd 00 0b 2a864886f70d010c050103 a989f3015f5087fecae98435ad09fc73

Le 2e octet (en rouge) code la longueur du l'entry-salt qui commence à l'octet 4 (en bleu). Les 16 derniers octets représentent la chaîne de caractères password-check, chiffrée en 3DES CBC avec une clé dérivée du Master Password (qui peut être vide), du global-salt et de l'entry-salt. La valeur 2a86...0103 encode l'OID de l'algorithme utilisé, nous y reviendrons.

Ce format est partiellement décrit dans le code source (keydb.c) de la bibliothèque NSS de Netscape/Mozilla [8]. Pour la première fois, dès 1999, ce sont [5] et [6] qui décrivent l'algorithme de dérivation ; reprenons globalement la notation de [5], voir également la figure 1 :

HP = SHA1(global-salt | MasterPassword)

avec HP for « hashed password », | pour « concaténation », PES = Version avec bourrage (padded) de entry-salt (ES), complété à 20 octets avec des zéros.

CHP = SHA1( HP | ES )

k1 = HMAC-SHA1( key=CHP, msg=PES | ES )

tk = HMAC-SHA1( CHP, PES )

k2 = HMAC-SHA1( CHP, tk | ES )

k = k1 | k2

les 24 premiers octets de k sont la clé 3DES EDE3 et les 8 derniers l'IV.

Cet algorithme de dérivation de clé (voir figure 1), spécifique à Netscape, est implanté dans secpkcs5.c [9].

On vérifie donc la valeur de cette clé en déchiffrant les données de l'enregistrement password-check.

Soit, avec les valeurs global-salt et entry-salt

GS=315aa1088a0c1fba32cf613eece884a02a9ac0cd, ES=ad6f1016c8bf0beeeeeee3add1bf05c275da42fd

et un Master Password égal à MISC*, les valeurs pour l'algorithme 3DES résultantes sont :

key = adb16943c31682b7f0bf8d0062ee15a059116d5486c36388, IV = add8570c39fa4399

ainsi selon la syntaxe PyCrypto :

DES3.new( key, DES3.MODE_CBC, iv).decrypt(a989f3015f5087fecae98435ad09fc73)

la valeur en clair est bien password-check\x02\x02, avec le bourrage (ici, de type PKCS) nécessaire à DES, dont la taille des données doit être multiple de 64 bits.

Décodage et déchiffrement de la clé privée

Vous avez bien sûr remarqué la valeur f8000000000000000000000000000001 qui est à la fois l'identifiant d'un enregistrement dans key3.db et est également présente dans chaque triplet des valeurs username et password étudiées précédemment et stockées dans signons.slite. Vous avez deviné : c'est cet enregistrement de key3.db qui contient la clé privée permettant de déchiffrer les valeurs protégées dans signons.slite.À nouveau [5] et [6] nous aide à interpréter la valeur de la base de données dont la clé est f8000000000000000000000000000001 et dont la valeur, d'une longueur est de 147 octets (0x93), est mise en forme ci-dessous :

03 00 01 00 30818c 3028 060b 2a864886f70d010c050103 3019 0414 a8db682ac51cfad8c06664fe9deb5283073b33ee 0201 01 0460

72d5636049d4af9eeadaf7eb0dc1710a62d5362fe4086dcc0495e5ec8e96c23c56b72a552e17756141ae80854d6fd03ecdc2c8f83d2c02d4c3f36e7e2b906f2c70a8cf571a06666e53f241780f9e39815e7d840e97e434614ac20ec09002e861Le 2e octet (en vert) est la longueur d'un sel (ici nul) . Le 3e octet la longueur du nom de la clé, ici réduit à l'octet nul (en bleu). Ensuite commence la clé privée, encodée en ASN1. Les types ASN1 sont mis en rouge, et voici ci-dessous la version décodée :

SEQUENCE {

SEQUENCE {

OBJECTIDENTIFIER 1.2.840.113549.1.12.5.1.3 // PKCS#5 pbeWithSha1AndTripleDES-CBC

SEQUENCE { // like MacData from PKCS#12v1

OCTETSTRING a8db682ac51cfad8c06664fe9deb5283073b33ee // used as entry-salt

INTEGER 1 // iteration count

}

}

OCTETSTRING 72d5636049d4af9eeadaf7eb0dc1710a62d5362fe4086dcc0495e5ec8e96c23c56b72a552e17756141ae80854d6fd03ecdc2c8f83d2c02d4c3f36e7e2b906f2c70a8cf571a06666e53f241780f9e39815e7d840e97e434614ac20ec09002e861

}

Le 2ème OCTETSTRING (72d5...e861) est à nouveau chiffré avec 3DES CBC, et le 1er OCTETSTRING (a8db...33ee) doit être utilisé comme valeur d'entry-salt pour en dériver la clé 3DES et son IV, de la même manière que pour la procédure de vérification du Master Password.

Ce format de données est finalement très proche de PKCS#12 V1.0, datant de 1999 [10]. Ainsi, l'IOD 1.2.840.113549.1.12.5.1.3 (pbeWithSha1AndTripleDES-CBC)correspond à la chaîne d'octets remarquée précédemment (2a864886f70d010c050103). PBE signifie Password Based Encryption, mais l’algorithme de dérivation décrit plus haut est bien spécifique à Netscape et non conforme à PKCS#5 [11] et PKCS#12 qui définissent les formats et ces algorithmes, dont le plus couramment utilisé aujourd’hui est PBKDF2.

Continuons, après déchiffrement avec

clé = aefa8fb2d3c9377d2c7e43af195c30b7d87750265cca7d21 et l'IV = 0342a42c8eef355c,

nous obtenons la valeur

3058 020100 300d 0609 2a864886f70d010101 0500 0444

3042020100021100f8000000000000000000000000000001020100021813c1e53d51a1e60bc79419f7d59107ef97976d075832a45b020100020100020100020100020115 060606060606

qui est une nouvelle fois au format ASN1, ce qui donne :

SEQUENCE {

INTEGER 0

SEQUENCE {

OBJECTIDENTIFIER 1.2.840.113549.1.1.1 // (PKCS#8)

NULL

}

OCTETSTRING 3042020100021100f8000000000000000000000000000001020100021813c1e53d51a1e60bc79419f7d59107ef97976d075832a45b020100020100020100020100020115

}

et en décodant une dernière fois en ASN1 la chaîne d'octets :

SEQUENCE {

INTEGER 0

INTEGER00f8000000000000000000000000000001

INTEGER 0

INTEGER13c1e53d51a1e60bc79419f7d59107ef97976d075832a45b //longueur : 0x18 = 24 octets

INTEGER 0

INTEGER 0

INTEGER 0

INTEGER 0

INTEGER 21

}

On obtient enfin la clé privée tant recherchée, après avoir transformé le 2ème entier en une séquence qui doit faire 24 d'octets ! Il s'agit donc de la clé 3DES CBC qui permet de décoder les valeurs username et password. Mais, où trouver l'IV de 8 octets nécessaire au mode CBC ?

Déchiffrement des valeurs username et password

Revenons à l'objectif initial : déchiffrer les données stockées dans signons.sqlite. Nul besoin d'attendre plus longtemps, les 8 octets parmi les triplets contiennent bien l'IV à utiliser pour déchiffrer la 3ème suite d'octets de chaque triplet, ici 92ff4b0f04861d11.

SEQUENCE {

OCTETSTRING f8000000000000000000000000000001

SEQUENCE {

OBJECTIDENTIFIER 1.2.840.113549.3.7 // DES-EDE3-CBC

OCTETSTRING c44c2e3a2493549a

}

OCTETSTRING 92ff4b0f04861d11

}

Ainsi, avec les paramètres suivants :

key = 13c1e53d51a1e60bc79419f7d59107ef97976d075832a45b et iv = c44c2e3a2493549a

il devient possible de déchiffrer le username :

DES3.new( key, DES3.MODE_CBC, iv).decrypt(d5d99c7ea2e30132ef901a00805890bc)

ce qui donne : login\x03\x03\x03.Pour le mot de passe, le principe est identique, mais laissé en exercice. Une vue d'ensemble de toutes ces opérations est en figure 2.

Analyse du niveau de protection

Analysons maintenant l'algorithme de dérivation de clé via la figure 1. Bien sûr, sans « mot de passe principal », le niveau de protection est nul et le déchiffrement des login/mot de passe est immédiat.

Et combien faut-il d’opérations élémentaires cryptographiques pour tester un mot de passe lors d'une attaque par force brute ? Deux SHA1 et deux à trois HMAC-SHA1 par itération, les données dépendantes du mot de passe étant représentées par des flèches pleines, et données pré-calculables en pointillés. Cela est très peu, surtout que dès PKCS#5 v2 (1999), le nombre minimal d'itérations recommandé est 1000. À titre de comparaison, IOS 3 d'Apple (en 2009) utilisait 2000 itérations de PBKDF2 et 10000 avec IOS 4.0 pour protéger l'accès de l'iPhone [12].

Pourquoi une protection si modeste ? Certainement pour raison historique, car cet algorithme de dérivation de clé date de Netscape 4.x en 1997. Il faut se souvenir que les États-Unis interdisaient avant 2000 l'export de la cryptographie au-delà de 40 bits. Le code source de Netscape Communicator 5.0 a été publié en mars 1998 [13], mais sans la cryptographie. Netscape avec SSL RC4-128 bits (auparavant 40 puis 56 bits) a été autorisé à l'export seulement en mai 2000, avec la version 4.73.

Mais curieusement, après la description des formats et algorithmes faite en 1999, il faudra attendre 2006, et l'outil FireMaster [14] qui permet l'attaque par force brute du fichier key3.db. Cette fonctionnalité est intégrée dans John the Ripper depuis 2012 [15].

Afin d'être complets, Thunderbird et SeaMonkey utilisent la même protection des mots de passe. Il est à noter également qu'avec Windows 7 version entreprise, Firefox ne sauvegarde aucun mot de passe, même si l'option est activée.

Un dernier (algo)test pour la route ?

Il serait dommage de ne pas tester, à titre indicatif, combien de temps prend John the Ripper (1.7.9-jumbo-7) pour casser un mot de passe, non ? La machine utilisée est un ICore5/3230M @2.6 Ghz avec 6 Go de RAM, ce qui est loin d'être une formule 1. Le principe utilisé est de brute-forcer le « password check » vu plus haut.

Voici la ligne générée par l'outil mozilla2john.exe, à partir de key3.db, comme entrée pour john.exe :

> more firefox3.txt

/key3.db:$mozilla$*3*20*1*ad6f1016c8bf0beeeeeee3add1bf05c275da42fd*

11*2a864886f70d010c050103*16*a989f3015f5087fecae98435ad09fc73

*20*315aa1088a0c1fba32cf613eece884a02a9ac0cd

On trouve, dans l'ordre : l'entry-salt (20 octets), l'OID de l'algorithme (11 octets, inutile ici), la chaîne password-check\x02\x02 chiffrée (16 octets) et enfin le global-salt (20 octets).

> john.exe firefox3.txt

Loaded 1 password hash (Mozilla SHA-1 3DES [32/32])

MISC* (key3.db)

guesses : 1 time : 0:01:14:29 DONE (Thu Aug 1 20:26:02 2013) c/s : 782491 trying : MINgk - MIGUv

Conclusion

Nous avons appris avec cet article comment reconnaître des données en Base64 et ASN1, les formats et algorithmes utilisés pour protéger les mots de passe enregistrés par Mozilla. Il faut retenir que l'algorithme de dérivation de la clé utilisant le « Mot de passe Principal » de Mozilla est loin des standards actuels et donc à la portée d'une attaque par force brute.Je remercie le site root-me.org pour l'idée de cet article et ses nombreux challenges, et le Dr Stephen Henson pour les précisions historiques. Merci enfin aux relecteurs pour leurs conseils.

Références

[1] Mozilla support, Password Manager: http://support.mozilla.org/en-US/kb/password-manager-remember-delete-change-passwords, http://support.mozilla.org/en-US/kb/profiles-where-firefox-stores-user-data, http://support.mozilla.org/en-US/kb/use-master-password-protect-stored-logins

[2] ASN1 DER encoding : http://en.wikipedia.org/wiki/Distinguished_Encoding_Rules#DER_encoding

[3] Berkeley DB 1.85 source code : http://download.oracle.com/berkeley-db/db.1.85.tar.gz

[4] http://oid-info.com/get/1.2.840.113549.3.7

[5] Dr. Stephen Henson, August 4th 1999 : http://marc.info/?l=openssl-dev&m=93378860132031&w=2

[6] Into the Black Box: A Case Study in Obtaining Visibility into Commercial Software, D. Plakosh, S. Hissam, K. Wallnau, March 1999, Carnegie Mellon University

: http://www.sei.cmu.edu/library/abstracts/reports/99tn010.cfm

[7] firepwd.py : https://github.com/lclevy/firepwd

[8] Private key database code from Netscape Security Service (NSS) library, février 2001, https://ftp.mozilla.org/pub/mozilla.org/security/nss/releases/NSS_3_2_1_RTM/src/nss-3.2.1/mozilla/security/nss/lib/softoken/keydb.c

[9] PKCS #5 Password based encryption code, NSS Library, janvier 2001

: https://ftp.mozilla.org/pub/mozilla.org/security/nss/releases/NSS_3_2_1_RTM/src/nss-3.2.1/mozilla/security/nss/lib/softoken/secpkcs5.c

[10] PKCS#12v1, Personal Information Exchange Syntax Standard, RSA labs : ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-12/pkcs-12v1.pdf

[11] PKCS#5v2, Password-Based Cryptography Standard, RSA labs, March 1999 : ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-5v2/pkcs5v2-0.pdf

[12] Advanced Password Cracking, Vladimir Katalov, décembre 2010 : http://blog.crackpassword.com/2010/12/cracking-blackberry-backups-is-now-slower-but-still-possible-thx-to-gpu-acceleration/

[13] Netscape sets source code free, mars 1998 : http://news.cnet.com/2100-1001-209666.html

[14] FireMaster, Nagareshwar Talekar, janvier 2006 : http://seclists.org/basics/2006/Jan/18

[15] John the Ripper 1.7.9-jumbo-6 release, juin 2012 : http://www.openwall.com/lists/john-users/2012/06/29/1