Fuzzing, de la génération au crash

Magazine
Marque
MISC
HS n°
Numéro
9
Mois de parution
juin 2014
Spécialité(s)


Résumé

Le fuzzing consiste à envoyer, de façon massive et automatisée, des données à un programme afin d'y identifier des vulnérabilités. Ce retour d'expérience devrait vous éviter quelques écueils et montrer comment des logiciels réputés peuvent succomber à cette technique peu coûteuse.


Body

1. Un peu de contexte

Cette technique de recherche de vulnérabilités a été identifiée dans les années 80. Tout commença par une soirée orageuse, alors qu'un informaticien connecté via RTC à sa station de travail était perturbé par l'insertion de caractères bizarres causés par les mauvaises conditions météorologiques. Mais les outils Unix basiques qu'il utilisait étaient encore plus perturbés que lui. En effet, certains d'entre eux plantaient brutalement. Surpris par ce comportement et supposant l'existence d'un gisement inexploité de défauts logiciels, il lança la première campagne connue de fuzzing (« An Empirical Study of the Reliability of UNIX Utilities » ftp://ftp.cs.wisc.edu/paradyn/technical_papers/fuzz.pdf). Les résultats furent impressionnants, certains outils plantant sur chacun des systèmes d'exploitation testés et d'autres déclenchant un plantage complet du système d'exploitation.

Puis, cette technique fut généralisée et utilisée pour des services réseaux. Elle permit assez rapidement d'identifier plusieurs vulnérabilités majeures, dont le célèbre « IIS .printer overflow » (MS01-023) découvert par la société eEye. Les curieux seront ravis d'apprendre que les compléments du livre « The Shellcoder's Handbook » (http://media.wiley.com/product_ancillary/83/07645446/DOWNLOAD/Source_Files.zip) incluent l'outil RIOT.exe utilisé à l'époque par Riley Russel. De nos jours, le fuzzing est principalement utilisé pour identifier des vulnérabilités dans les applications clientes (navigateurs, lecteurs PDF, lecteurs Flash…).

Mais le principe s'applique aussi aux applications Web, par l'envoi automatisé et massif de données réputées dangereuses (../../../etc/passwd, ' OR '1' --, …) pour chacun des paramètres identifiés. Les paramètres à prendre en compte ne se limitent alors pas au triplet « GET, POST, Cookies ». En effet, plusieurs en-têtes HTTP sont connus pour être traités directement par des applications. Citons à titre d'exemple Accept-Language (utilisé avec include() sur un fichier de traduction) ou Authorization (transmis à un serveur LDAP).

D'autres cas d'utilisation plus atypiques existent, comme par exemple le fuzzing de compilateurs C (csmith http://embed.cs.utah.edu/csmith/, CCG https://github.com/Merkil/ccg) ou d'extensions Oracle développées en PL/SQL (http://seclists.org/fulldisclosure/2006/Dec/114).

2. Choix des cibles

Dans le cas le plus simple, la cible de la campagne de fuzzing consiste en un produit fini, comme un navigateur ou un produit d'entreprise (solution de sauvegarde, de télé-déploiement…). Dans ce cas comme dans celui d'un projet doté de contraintes souples (R&D interne, hobby, maintien à jour des compétences), il peut être intéressant de définir des sous-cibles, plus faciles à isoler et à tester.

Ces sous-cibles sont définies par soit des fonctionnalités spécifiques (interaction avec le réseau, lecture d'images, gestion du DOM…), soit des bibliothèques tierces intégrées au produit fini visé. Dans le cas où la cible est limitée à des bibliothèques tierces, d'autres aspects positifs sont à considérer : moindre quantité de code à tester (ce qui intéressant si la technique d'instrumentation de la cible a un impact élevé en termes de performances, comme avec « Valgrind Memcheck »), possibilité de trouver des vulnérabilités impactant simultanément plusieurs produits finis (une faille dans libxslt2 permet d'attaquer Chrome, PostgreSQL et PHP), etc. Toutefois, certains revers existent. Le plus gênant est la possibilité d'un écart significatif entre la bibliothèque testée et celle déployée. Dans ce cas-là, soit du temps est gaspillé à tester des fonctionnalités non présentes dans le produit fini (« Off by one in rc4_decrypt() » impactant libxslt2, mais pas Chrome https://bugzilla.gnome.org/show_bug.cgi?id=675917), soit le code ajouté lors de l'intégration n'est pas couvert lors du fuzzing (Adobe Reader utilise une version largement modifiée du moteur XSLT open source Sablotron).

Enfin, la plupart des coûts initiaux d'une campagne de fuzzing peuvent être mutualisés. Il est souvent possible de réutiliser les données intermédiaires d'une campagne passée (jeu de tests initial en cas de mutation, grammaire en cas de génération, outils de surveillance et de suivi) pour une autre. Le coût global des campagnes ultérieures est alors très faible. La campagne de fuzzing utilisée comme exemple principal du présent article porte sur le langage XSLT. Dans ce cas, les étapes mutualisées entre l'ensemble des cibles sont la production du jeu de test (collecte du jeu de test initial, définition et outillage du schéma de mutation) ainsi que le développement d'un outil spécifique de minimisation automatique.

3. Production des jeux de test

3.1 Statique

La plus basique des techniques de production de jeux de test consiste à transmettre à la cible des données statiques, en dehors de tout contexte relatif à la cible. L'exemple le plus efficace que je connaisse (et il ne l'est pas beaucoup) consiste à envoyer un nombre incrémental d'octets 0xFF à un port TCP ou UDP.

Cela permet de planter quelques obscurs daemons, et parfois de récupérer des données présentes en mémoire vive. Si vous ne croyez pas que cette technique puisse encore marcher de nos jours, testez-la donc sur chacun des services UDP et TCP d'un gros réseau hétéroclite (université, opérateur de communication, infrastructure SCADA) dont vous avez la charge. Mais ne venez pas vous plaindre si un de vos onduleurs ne répond plus...

3.2 Aléatoire

Cette technique est triviale à implémenter et est, dans l'absolu, applicable à l'ensemble des formats et protocoles. Elle peut donc sembler un bon choix. Toutefois, la quantité de code traversé lors des tests sera probablement très faible et limitée aux fonctionnalités les plus basiques de traitement des entrées. Malgré cette limitation, des campagnes de fuzzing ayant utilisé des données aléatoires et ayant produit de bons résultats existent. C'est le cas de la campagne originelle des années 80, portant sur des utilitaires UNIX et utilisant un générateur (« fuzz.c », ftp://ftp.cs.wisc.edu/pub/paradyn/fuzz/fuzz-original.tar.gz) produisant des caractères à partir d'appels à rand() % 255.

Il est à noter que l'introduction aléatoire de modifications (comme le fait « ProxyFuzz » http://www.secforce.com/media/tools/proxyfuzz.py.txt) sera décrite dans une section ultérieure, « Schéma de mutation ».

3.3 Mutation

La production de jeux de test par mutation consiste à appliquer des modifications, plus ou moins complexes, à un jeu de test initial. Deux des points indispensables à une campagne de fuzzing par mutation efficace sont donc :

- la constitution du jeu de test initial ;

- la définition et l'outillage du schéma de mutation.

3.3.1 Jeu de test initial

Si on prend le cas de ma campagne visant les interpréteurs XSLT, les sources suivantes avaient été utilisées : organismes de normalisation (W3C et NIST http://www.w3.org/Style/XSL/TestSuite/), projets destinés à des vérifications d'interopérabilité (OASIS https://www.oasis-open.org/committees/documents.php?wg_abbrev=xslt), jeux de test inclus dans le code source des interpréteurs XSLT open source, bug trackers publics, forums et listes de diffusion (TechNet, StackOverflow, W3C…), fichiers exotiques (quine, attracteur de Lorenz, traceur d'exécution…, http://incrementaldevelopment.com/xsltrick/ et http://www2.informatik.hu-berlin.de/~obecker/XSLT/) et collecte massive via des moteurs de recherche (avec filetype:xsl sous Google). Soit un total supérieur à 7 000 fichiers XSLT.

Dans l'idéal, les fichiers résultant de la visite des différentes sources sont stockés séparément, afin de bénéficier de traitements postérieurs spécifiques (comme la suppression d'une mise au format HTML). Il peut aussi être utile de vérifier leur conformité, dans le cas présent leur validité (aux sens XML et XSLT). Cette étape doit être réalisée avec mesure, certains fichiers invalides ayant légitimement leur place (dont ceux ayant permis d'identifier des vulnérabilités dans des versions précédentes ou dans d'autres implémentations).

Une fois ces fichiers collectés, il est important de limiter le jeu de test aux seuls fichiers nécessaires à l'obtention d'une couverture optimale du code de la cible. Sur un jeu de fichiers quelconques, il n'est pas rare de pouvoir éliminer 80 à 90 % des échantillons tout en conservant une même couverture de code !

De nombreux outils de compilation permettent de réaliser un calcul de couverture de code, du plus classique (« gcov », http://gcc.gnu.org/onlinedocs/gcc/Gcov.html) au plus moderne (« ASanCoverage », http://code.google.com/p/address-sanitizer/wiki/AsanCoverage). Le calcul de couverture de code peut aussi être réalisé sans que le code source soit à disposition, que ce soit avec un débogueur ou une solution d'instrumentation comme « DynamoRIO » (http://dynamorio.org/). Ensuite, la sélection est réalisée selon un algorithme de type « Greedy Search », non optimal, mais rapide et simple.

Par ailleurs, le jeu de test initial n'est pas statique et doit être enrichi au cours de la campagne de fuzzing, par l'ajout de fichiers obtenus par génération (s'ils augmentent la quantité de code couvert) et l'ajout de fichiers ayant servi à identifier des plantages au cours de la présente campagne.

3.3.2 Schéma de mutation

De nombreux schémas de mutation existent. Les plus usuels sont :

- la modification d'un bit (« bit flipping ») ;

- le remplacement par des valeurs « à risque » issues d'une liste en dur (Burp Suite Pro) ;

- le remplacement par des séquences aléatoires ;

- l'utilisation des spécificités du format (par exemple, manipuler le DOM d'un document XML et déplacer/ajouter/supprimer des nœuds).

Deux exemples montrant que même des mutations simples sont effectives :

- en 2010, Charlie Miller publia « Babysitting an army of monkeys » (http://fuzzinginfo.files.wordpress.com/2012/05/cmiller-csw-2010.pdf). Le schéma de mutation était simple : remplacer un nombre aléatoire d'octets par des données aléatoires de même longueur. Cela a suffi à identifier plusieurs dizaines de plantages classifiés comme exploitables ;

- en 2012, Gynvael Coldwin mena une campagne de fuzzing visant ntfs.sys (http://j00ru.vexillium.org/?p=1272), se contentant de mutations de type « bit flipping ». Au bout de seulement 17 heures, une première vulnérabilité fut identifiée (CVE-2013-1293 et MS13-036 : http://technet.microsoft.com/fr-fr/security/bulletin/ms13-apr).

Personnellement, j'utilise régulièrement « Radamsa » (http://code.google.com/p/ouspg/wiki/Radamsa) qui permet de couvrir la plupart de mes besoins de mutation. Je l'ai appliqué avec succès sur les cibles suivantes :

- Microsoft Excel et le format texte SLK (CVE-2011-1276 / MS11-045) ;

- une « appliance » bancaire utilisant un protocole réseau binaire propriétaire ;

- de nombreux logiciels réalisant du traitement XSLT, dont Firefox (CVE-2012-3972 et CVE-2012-0044), Chrome (CVE-2012-2825, CVE-2012-2870 et CVE-2012-2871), Adobe Reader (CVE-2012-1525 et CVE-2012-1530), MSXML (CVE-2013-0007) et Oracle (CVE-2013-3751).

Son usage est très simple, la définition des mécanismes sous-jacents étant par défaut gérée automatiquement. L'exemple suivant génère dans le répertoire out 100 fichiers mutés à partir des originaux contenus dans le répertoire samples :

$ radamsa -o out/feeling_lucky-%n.xsl -n 100 -m out/.meta samples/*.xsl

3.3.3 Correction après mutation

Après mutation des données originales, il est fréquent qu'une correction soit nécessaire. Cela peut consister au calcul du champ Content-Length d'une requête HTTP, d'un CRC ou d'une signature. Ce mécanisme de correction post-mutation (traditionnellement appelé « fixup ») permet généralement d'obtenir une meilleure couverture du code visé.

Dans le cas de ma campagne XSLT, les fichiers mutés n'étaient pas forcément des fichiers XML bien formés. Deux choix sont alors possibles : tester par ailleurs la validité des fichiers mutés et n'utiliser que ceux valides, ou utiliser l'ensemble des fichiers. J'ai opté pour la seconde solution, cela permettant accessoirement de tester la robustesse de l'analyseur XML intégré à chaque interpréteur XSLT.

3.4 Génération

3.4.1 Par bloc

La principale faiblesse de la production par mutation est que la quantité de code couvert ne dépend pas que du jeu de test original et du schéma de mutation, mais aussi des contraintes techniques définissant la validité d'un cas de test. Typiquement, les protocoles binaires fortement imbriqués ne peuvent pas être testés en profondeur par mutation, car les champs « taille » seront pour la plupart invalides et interromprons le traitement. Cela comprend entre autres ASN.1 (LDAP, X.509...) et la plupart des formats de type RPC.

De plus, savoir si une section de données représente un entier, un séparateur ou une chaîne de texte permettrait d'apporter des mutations spécifiques à la valeur originale. Typiquement, « A x 5000 » serait réservé aux chaînes de texte et les variations autour de MAX_INT réservées aux entiers.

Pour toutes ces raisons, le fuzzing par bloc a été introduit. En 2002, David Aitel publia « The Advantages of Block-Based Protocol Analysis for Security Testing » (http://pentest.cryptocity.net/files/fuzzing/advantages_of_block_based_analysis.pdf) accompagné de l'outil « SPIKE ». Bien que présentant des avantages indéniables, l'outil resta assez peu utilisé, principalement car il était peu documenté et complexe à adapter à des besoins spécifiques. En 2007, Pedram Amini et Aaron Portnoy publient Sulley, qui reprend les principes (et la syntaxe !) de Spike tout en étant largement documenté (cf. le livre « Fuzzing : Brute Force Vulnerability Discovery »), partiellement graphique (l'interface Web de suivi), facile d'accès et doté de fonctionnalités additionnelles intéressantes comme la capture du trafic réseau ou la restauration de points d'arrêt VMware. Le commun des mortels pouvait enfin s'adonner aux plaisirs de la génération par bloc !

Un des avantages de cette technique est de permettre un fonctionnement incrémental, très utile dans le cadre d'un format propriétaire. Les premières définitions sont très basiques, par exemple directement copiées depuis une capture réseau. Puis le format de quelques données évidentes est précisé (chaîne, entier, délimiteur…). Puis les champs décrivant une taille ou un décalage sont identifiés et définis. Puis des ensembles de valeurs discrètes sont extraits par analyse statique du binaire et incorporés, etc.

Cette stratégie de production de jeux de test n'a pas du tout été utilisée au cours de ma campagne XSLT, car je ne voyais pas comment décrire du code XSLT sous forme de blocs. Je l'ai par contre utilisée avec succès dans le cadre d'une précédente campagne de fuzzing, portant sur LDAPv3. Une des vulnérabilités alors identifiées est à mon avis typique de la production par bloc : c'était un débordement du tas durant le traitement du jeton SP-NEGO d'une authentification GSSAPI lors d'une demande de connexion. Soit 7 couches ASN.1 empilées, chacune contenant un champ « taille » et diverses constantes !

3.4.2 Par grammaire

Parfois, il est possible de décrire l'ensemble des possibilités offertes par un protocole ou un format. Cela se traduit généralement par la possession d'une grammaire, que ce soit en BNF (Forme de Backus-Naur : http://fr.wikipedia.org/wiki/Forme_de_Backus-Naur) comme dans une grande partie des RFC ou sous forme de DTD (Document Type Definition) ou de XSD (XML Schema Definition) dans le cas de documents XML.

Mais bon, je n'ai jamais trouvé de vulnérabilité en utilisant uniquement de la production de jeux de test à partir d'une grammaire. Par contre, il est utile de mesurer le code couvert par les fichiers produits à partir d'une grammaire, et d'intégrer au jeu de test initial (lors d'une production par mutation) les fichiers les plus intéressants.

3.4.2.1 BNF

Une fois la grammaire obtenue (à partir du site de l'IETF : http://www.rfc-editor.org/rfc-index.html pour les RFC), il faut l'utiliser pour générer des jeux de test. Pour BNF, l'outil généraliste « BNF example generator » (disponible en tant qu'application Web et script Perl, http://www.cems.uwe.ac.uk/~cjwallac/apps/tools/bnf/) marche assez bien. Mais un outil dédié à la génération depuis une grammaire BNF dans le cadre d'une démarche de fuzzing existe. Cet outil, développé par les auteurs de « radamsa », est nommé « blab » (http://code.google.com/p/ouspg/wiki/Blab). D'après ses auteurs, l'outil permet aussi d'écrire de la poésie moderne ;-) Pour ne rien gâcher, il est livré avec une vingtaine de définitions couvrant (éventuellement partiellement) JSON, JavaScript, HTML, CSS, les expressions régulières, les URL, et bien plus. À titre d'exemple, la commande suivante génère 42 entiers signés :

$ blab -e num.integer -o - -n 42

Parfois, une grammaire BNF publiquement disponible n'est pas utilisable directement sous « blab » en raison de différences de syntaxe (comme par exemple l'utilisation de ::= au lieu de =). Ce problème est habituellement facilement éliminable avec sed, awk ou n'importe quel autre instrument de torture aisément accessible depuis une ligne de commandes.

3.4.2.2 DTD et XSD

Si l'on dispose d'une grammaire de type XML (DTD ou XSD), de nombreux outils permettent de générer des fichiers conformes à cette grammaire. Cela inclut la plupart des IDE, dont Eclipse, IntelliJ et Visual Studio. Il est à noter que certaines distributions Linux n'incluent pas par défaut le plugin « Eclipse XML Editors and Tools » permettant la génération à partir de DTD et XSD. Dans cette situation, le menu Help > Install new software devrait aider...

Pour de la production de masse n'utilisant pas un logiciel commercial, c'est plus compliqué. Une des seules possibilités utilisables que je connaisse est « CAM Processor » (http://sourceforge.net/projects/camprocessor/). Mais il ne supporte que XSD et son utilisation n'est pas très intuitive (conversion intermédiaire au format CAM...). Heureusement, un tutoriel bien conçu est disponible (http://www.oasis-open.org/committees/download.php/29661/XSD%20and%20jCAM%20tutorial.pdf). D'ailleurs, si vous connaissez un outil de génération à partir de fichiers DTD et XSD qui soit fiable, open source et utilisable depuis une ligne de commandes, je suis preneur !

3.5 Restriction à une liste de valeurs

Que ce soit pour la mutation ou la génération, il est tentant de contraindre à une liste restreinte certaines des données envoyées. Cela peut en effet être intéressant si le code couvert par les autres valeurs est supposé nul ou faible. Deux exemples courants sont les méthodes HTTP et les IOCTL des drivers Windows. Une phase d'analyse statique est donc parfois nécessaire afin d'identifier les valeurs supportées. Dans le cas de l'énumération des IOCTL valides, un outil comme Driverlib (script pour ImmunityDbg) permet d'obtenir des résultats rapides.

Malheureusement, dans le domaine du fuzzing, la plupart des « optimisations » sont un compromis entre la rapidité des tests et la surface couverte. Dans le cas précis des méthodes HTTP, l'utilisation d'une liste de valeurs connues ne permettrait pas de trouver CVE-2001-0747 (« Netscape Enterprise Server HTTP Method Name Buffer Overflow Vulnerability » : http://www.exploit-db.com/exploits/22230/). En effet, le critère de déclenchement de la faille est l'envoi d'une méthode HTTP d'une longueur supérieure à 4022 caractères.

Autre exemple : j'ai découvert au cours d'une campagne de fuzzing portant sur l'outil de monitoring BMC PatrolAgent que chaque paquet émis par le client contenait un numéro de version et que le serveur rejetait tout paquet ayant un numéro de version soit invalide, soit supérieur au sien. Il peut donc être tentant de se limiter à une seule valeur valide. Heureusement, je ne l'ai pas fait. En effet, une des vulnérabilités identifiées était justement... une erreur de chaîne de format lors du traitement d'un numéro de version invalide (CVE-2008-5982 : http://www.zerodayinitiative.com/advisories/ZDI-08-082/).

Fort de cette expérience, je m'applique désormais à réaliser deux passes distinctes. La première passe se limite aux emplacements que je souhaite à terme restreindre à une liste de valeurs valides (champs « taille », numéro de version, méthode HTTP…) et la seconde utilise des valeurs statiques pour ces emplacements précédemment testés, afin de se focaliser sur les autres données.

3.6 Mutation des champs représentant une taille

La technique précédemment décrite (utilisation de deux passes distinctes) s'applique tout aussi efficacement à l'ensemble des champs dont la valeur représente une taille, que ce soit dans de l'ASN.1 (tester du LDAP, quelle horreur !), dans un format « Type Length Value » (très courant dans les protocoles binaires propriétaires) ou dans une requête HTTP (en-tête Content-Length). Les applications web sont généralement insensibles à ce genre de mutation, mais pas forcément le serveur web lui-même (ou le reverse-proxy, le WAF…). À titre d'exemple, la vulnérabilité « CVE-2008-4478 » (« Novell eDirectory dhost.exe Content-Length Header Heap Overflow Vulnerability » : http://www.zerodayinitiative.com/advisories/ZDI-08-063/) a été trouvée avec Sulley en utilisant un fichier de définition contenant le code suivant :

s_static(‘Content-Length: ‘)
s_size(‘body’,format=’ascii’, signed=True, fuzzable=True)

4. Préparation des tests

4.1 Performances

Ma principale astuce concernant les performances globales d'une campagne de fuzzing consiste à ne pas tester un produit final, mais plutôt les bibliothèques le composant. Lors de la campagne XSLT, j'ai donc testé libxslt2 pour trouver des vulnérabilités dans Chrome, MSXML pour Internet Explorer et Sablotron pour Adobe Reader. Le gain en performances est énorme (jusqu'à x200 pour Adobe Reader), mais d'autres avantages existent. On peut citer un contexte d'exécution plus propre (le coût de lancement de la cible étant très faible, on la relance pour chaque test) et donc une meilleure reproductibilité, ainsi que la possibilité de travailler en boîte blanche (accès au code et aux symboles, instrumentation facilitée) dans le cas d'un logiciel propriétaire utilisant un composant open source (comme pour Adobe Reader).

4.2 Raccourcis d'exécution

Un raccourci intéressant consiste à contourner (au niveau du code source ou du binaire) les mécanismes irréalisables avec les outils employés (authentification OAuth2…) ou complexes à implémenter (signature des entrées, calcul d'un CRC non standard...). Une fois une vulnérabilité trouvée, il sera toujours temps d'implémenter un PoC plus complexe prenant en compte l'ensemble des contraintes techniques permettant d'accéder au code vulnérable sur une version standard.

D'ailleurs, les auteurs de Sulley ont utilisé cette technique lors de leur campagne de fuzzing visant le produit « Trend Micro Control Manager », comme en témoigne cet extrait du fichier requests/trend.py :

# XXX - CRC is non standard, nop out jmp at 0041EE99 and use bogus value:
#s_checksum(«body», algorithm=»crc32»)
s_static(«\xff\xff\xff\xff»)

4.3 Instrumentation et débogage

Cette section mériterait un article à elle seule… L'offre logicielle est très riche, les habitudes de travail comptent énormément et les outils dépendent du niveau de compétence de leur utilisateur. Donc pas de « meilleure solution » universelle.

Sous Unix, Asan (http://code.google.com/p/address-sanitizer/) et Valgrind (http://valgrind.org/) sont très pratiques pour le suivi en temps réel, avec gdb et ses compléments (comme le « Data Display Debugger » : https://www.gnu.org/software/ddd/) pour l'analyse manuelle. Sous Windows, gflags.exe(http://msdn.microsoft.com/en-us/library/windows/hardware/ff549557(v=vs.85).aspx) permet d'instrumenter les fonctions de gestion de la mémoire et de vérifier l'utilisation du tas, afin de détecter les corruptions mémoires elles-mêmes et non leurs conséquences, un luxe appréciable ;-) Enfin, « Dr Memory » (http://www.drmemory.org/) est une solution d'instrumentation reposant sur « DynamoRIO », gratuite et fonctionnant sous Linux et Windows. Très très pratique quand on cherche à appliquer une analyse de type « Valgrind Memcheck » à une application Windows pas trop complexe !

5. Déroulement des tests

5.1 Collecte d'information sur les plantages

Si la cible tourne sous un débogueur (ce qui est recommandé, à moins que la cible soit instrumentée, par exemple avec ASan ou GFlags.exe), une question importante se pose : faut-il intercepter toutes les exceptions ou seulement les finales (dites « de deuxième chance ») ? Pour des applications complexes utilisant couramment les exceptions de façon légitime (par exemple, Microsoft Office), ma stratégie consiste à ne traiter que les exceptions finales, tout en journalisant l'ensemble des exceptions.

De plus, il peut aussi être utile de surveiller et de journaliser les messages de débogage (à la « DebugView » de SysInternals : http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx). Cela peut servir à détecter des situations où l'application réagit de façon inhabituelle, et d'orienter les efforts de fuzzing vers ces sections potentiellement plus fragiles.

Dans le cas d'un plantage, il faut bien évidemment collecter le maximum d'informations techniques (stack trace, contenu des registres…), mais aussi commencer à préparer les étapes de reproduction et de minimisation. Cela peut consister à identifier les différences avec le fichier original (en cas de mutation) et à les intégrer aux informations relatives au plantage.

Il y a donc une quantité importante de données produites lors d'une campagne de fuzzing. J'ai essayé pas mal de moyens de les stocker et de les consulter (conservation sur le serveur de fuzzing et accès par SSH, envoi par mail de rapports préformatés…). J'utilise désormais un serveur de bases de données global, qui est la seule ressource « non jetable ». Ce serveur MySQL est donc maintenu, sauvegardé… alors que les instances de fuzzing elles-mêmes sont des machines virtuelles diverses et variées, hébergées ici ou là en fonction des promotions des différents fournisseurs. Chaque campagne possède sa propre base de données, des modifications spécifiques pouvant ainsi être facilement apportées au schéma initial.

Ce type de stockage permet d'accéder de façon variée à l'ensemble des données, que ce soit pour des recherches globales comme pour du suivi en temps réel. On peut imaginer des tâches planifiées qui envoient par mail un résumé quotidien, associées à une interface web de consultation en temps réel basée sur « Adminer » (http://www.adminer.org/). Il est aussi possible de transmettre directement la base de données au projet concerné, si l'analyse des plantages se fait chez eux (cas des campagnes réalisées pour le compte d'éditeurs de produits propriétaires).

5.2 Évaluation de la reproductibilité

Une de mes techniques, peu coûteuse et permettant de gérer efficacement les priorités lors de l'analyse manuelle, consiste à mesurer lors du fuzzing lui-même la facilité de reproduction du plantage et à remonter cette information avec les autres données lui étant associées. Par exemple, relancer 10 fois chaque cas de test menant à un plantage et le classifier entre « Très fiable » et « Peu fiable » selon les résultats. L'analyse manuelle se focalisera d'abord sur les plantages marqués comme très fiables, avec toutefois la possibilité d'isoler ceux ayant une reproductibilité faible, mais une stack trace identique. Cette technique fonctionne assez bien sur des projets contraints par le temps d'analyse humaine, par exemple un projet personnel de priorité basse ou une mission mal vendue ;-)

6. Minimisation en cas d'identification d'un crash

Un des désavantages de la production de cas de test par mutation est l'éventuelle large quantité de modifications apportées au cas de test original. En cas de minimisation manuelle, il faut donc revenir sur chacune de ces modifications pour identifier laquelle (ou lesquelles) occasionne le plantage.

Pour cela, savoir quelles étaient les données avant mutation est primordial. Il est donc pratique d'ajouter au cas de test muté une référence au fichier original. Dans le cas d'un mutateur « maison », cela peut consister à stocker le nom de fichier original dans une section n'impactant pas (ou pas beaucoup) le traitement. Par exemple, le champ « EXIF » des images JPG, l'extension « Comment » des images GIF ou le commentaire « <!-- --> » pour XML. Une solution indépendante du format ciblé consiste à produire un fichier permettant d'associer a posteriori fichiers mutés et originaux. L'option --meta de Radamsa propose exactement cela.

J'apprécie aussi de pouvoir visualiser graphiquement les différences entre fichiers mutés et originaux, par exemple avec l'éditeur « Meld » (http://meldmerge.org/). Une fois les modifications localisées, il est alors aisé de les analyser et de les inverser.

Par ailleurs, des solutions automatisées et ne nécessitant pas de connaître le fichier original existent, comme « delta » (généraliste http://delta.tigris.org/) et « tmin » (orienté fuzzing http://code.google.com/p/tmin/). Je n'ai jamais eu beaucoup de succès avec ces outils, probablement en raison d'un problème entre la chaise et le clavier. J'ai alors créé mon propre outil de minimisation automatique lors de la campagne XSLT, d'après une spécificité de XSLT : l'utilisation de blocs « xsl:template ». Le mécanisme de minimisation consiste à charger le fichier XSLT en mode DOM et générer des variantes omettant chacune un des blocs « xsl:template », puis de sélectionner comme base de l'itération suivante le plus petit fichier généré reproduisant le plantage. Cela n'épargne pas une finalisation manuelle, mais le plus gros du travail est ainsi fait automatiquement.

Conclusion

Deux conclusions, selon votre niveau d'expérience en fuzzing. Si vous n'en avez jamais fait, ne soyez pas intimidé, c'est facile et très gratifiant (pour l'ego comme pour le compte en banque ;-) ). Des solutions clés en main existent, comme « Basic Fuzzing Framework » (http://www.cert.org/vulnerability-analysis/tools/bff.cfm) proposé par le US-CERT et « SDL MiniFuzz File Fuzzer » (http://www.microsoft.com/en-us/download/details.aspx?id=21769) proposé par Microsoft. Le coût d'entrée se limite alors au lancement d'une machine virtuelle, au choix d'une cible et à un peu d'huile de coude. Vos premières cibles peuvent être des lecteurs de fichiers multimédias gratuits ou tout logiciel pas trop connu déployé dans votre environnement de travail usuel.

Si vous êtes plus expérimenté, rappelez-vous que la diversité prime. La plupart des études ont montré qu'un fuzzer ne trouvait jamais l'ensemble des plantages identifiés par ses concurrents (cf. les résultats de la compétition « Microsoft Fuzzing Olympics » : http://resources.sei.cmu.edu/library/asset-view.cfm?assetid=53564). Variez vos méthodes de production de jeux de test, n'hésitez pas à activer de temps en temps des algorithmes de mutation qui vous semblent stupides ou inutiles, isolez le code ciblé pour obtenir de gros gains de performances, automatisez au maximum… et bonne chance dans votre chasse aux bugs !

 



Article rédigé par

Par le(s) même(s) auteur(s)

Les derniers articles Premiums

Les derniers articles Premium

Bénéficiez de statistiques de fréquentations web légères et respectueuses avec Plausible Analytics

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Pour être visible sur le Web, un site est indispensable, cela va de soi. Mais il est impossible d’en évaluer le succès, ni celui de ses améliorations, sans établir de statistiques de fréquentation : combien de visiteurs ? Combien de pages consultées ? Quel temps passé ? Comment savoir si le nouveau design plaît réellement ? Autant de questions auxquelles Plausible se propose de répondre.

Quarkus : applications Java pour conteneurs

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Initié par Red Hat, il y a quelques années le projet Quarkus a pris son envol et en est désormais à sa troisième version majeure. Il propose un cadre d’exécution pour une application de Java radicalement différente, où son exécution ultra optimisée en fait un parfait candidat pour le déploiement sur des conteneurs tels que ceux de Docker ou Podman. Quarkus va même encore plus loin, en permettant de transformer l’application Java en un exécutable natif ! Voici une rapide introduction, par la pratique, à cet incroyable framework, qui nous offrira l’opportunité d’illustrer également sa facilité de prise en main.

De la scytale au bit quantique : l’avenir de la cryptographie

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Imaginez un monde où nos données seraient aussi insaisissables que le célèbre chat de Schrödinger : à la fois sécurisées et non sécurisées jusqu'à ce qu'un cryptographe quantique décide d’y jeter un œil. Cet article nous emmène dans les méandres de la cryptographie quantique, où la physique quantique n'est pas seulement une affaire de laboratoires, mais la clé d'un futur numérique très sécurisé. Entre principes quantiques mystérieux, défis techniques, et applications pratiques, nous allons découvrir comment cette technologie s'apprête à encoder nos données dans une dimension où même les meilleurs cryptographes n’y pourraient rien faire.

Les listes de lecture

11 article(s) - ajoutée le 01/07/2020
Clé de voûte d'une infrastructure Windows, Active Directory est l'une des cibles les plus appréciées des attaquants. Les articles regroupés dans cette liste vous permettront de découvrir l'état de la menace, les attaques et, bien sûr, les contre-mesures.
8 article(s) - ajoutée le 13/10/2020
Découvrez les méthodologies d'analyse de la sécurité des terminaux mobiles au travers d'exemples concrets sur Android et iOS.
10 article(s) - ajoutée le 13/10/2020
Vous retrouverez ici un ensemble d'articles sur les usages contemporains de la cryptographie (whitebox, courbes elliptiques, embarqué, post-quantique), qu'il s'agisse de rechercher des vulnérabilités ou simplement comprendre les fondamentaux du domaine.
Voir les 66 listes de lecture

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous