Blog Haypo

Aller au contenu | Aller au menu | Aller à la recherche

samedi 26 janvier 2008

Failles de sécurité liées à Unicode

La complexité du jeu de caractères Unicode est source de nombreuses failles de sécurité. Cet article présente quelques failles récentes pour illustrer les problèmes qu'on peut rencontrer.

Écriture bidirectionnelle (RLO et LRO)

Le premier type que je veux présenter n'est pas un bug d'Unicode, mais une fonctionalité ! On peut changer l'ordre dans lequel est écrit le texte. Un dieu du CSS, Stu Nicholls, l'utilise pour afficher son adresse email en clair, alors qu'en fait elle est écrite à l'envers dans la souce HTML ! Le style CSS est « unicode-bidi:bidi-override; direction: rtl; ».

Sauf que des malins ont pensé à utiliser cette fonctionnalité pour tromper l'œil humain en cachant l'extension d'un nom de fichier. L'article Deceptive file names under Vista (septembre 2007) montre comment une programme Windows (.scr) est affiché comme une image JPEG (.jpg) dans Windows Vista. Windows XP ne supporte pas cette fonctionnalité et affiche donc les codes de contrôle Right-to-left override (RLO, U+202E) et Left-to-right override (LRO, U+202D), montrant alors la supercherie.

Halfwidth and Fullwidth Forms

Les failles de type « directory traversal » outrepassent les mesures de sécurité et permettent de lire un fichier arbitraire. En PHP, on trouve souvent des failles du type « index.php?page=../../../../etc/passwd ». Les webmestres se protègent en interdisant la chaîne « ../ » dans le nom du fichier utilisé. Quelques fois, il est possible d'outrepasser cette protection en spécifiant le chemin complet du fichier. Une variante est de jouer entre les caractères « / » et « \ » selon le système d'exploitation. Certains serveurs et/ou systèmes d'exploitations acceptent également « .../ » dans le nom du fichier.

Ce type de bug est aujourd'hui connu et corrigé dans la majorité des serveurs. Dumoins, c'est ce qu'on pensait jusqu'à cette annonce : Unicode encoding can be used to bypass intrusion detection systems (juin 2007). L'idée est d'utiliser les caractères halfwidth et fullwidth de la plage Unicode U+FF01-U+FFEE. Le soucis est que les URL sont normalisées après avoir été validées !

Exemple de normalisation (décomposition canonique) avec Python 2.5 :

>>> from unicodedata import normalize
>>> char=normalize('NFKC', u'\uFF0E'); print "%r (%s)" % (char, ord(char))
u'.' (46)
>>> char=normalize('NFKC', u'\uFF0F'); print "%r (%s)" % (char, ord(char))
u'/' (47)
>>> char=normalize('NFKC', u'\uFF3C'); print "%r (%s)" % (char, ord(char))
u'\\' (92)

Séquence UTF-8 invalide

Il faut savoir que 7 ans plus tôt, un bug similaire avait déjà été découvert dans Microsoft IIS (octobre 2000). Cette fois-ci, le problème était la normalisation de l'encodage UTF-8. IIS était trop laxiste : il acceptait les séquences invalides, c'est-à-dire lorsqu'un code a une séquence plus longue en octets que la taille normale. Exemple : le caractère point « . » (U+2E) s'encode « 0x2E » en UTF-8, mais peut également être encodé (0xC0, 0xAE) (forme invalide).

Note : Le langage Java utilise d'ailleurs une forme non standard d'UTF-8 : le caractère nul est encodé volontairement (0xC0, 0x80) pour éviter qu'une chaîne soit tronqué par une fonction C bas niveau (telle que strcpy).

Confusion entre octet et caractère

Depuis que le jeu de caractères ASCII a été inventé, il existe une confusion entre la notion d'octet et de caractère. C'est encore plus vrai avec les jeux de caractères ISO-8859. La très grande majorité des programmes mélangent allègrement octets et caractères sans se poser de question. D'une manière générale, ce n'est pas trop gênant. On retrouve cette problématique lorsqu'on manipule du HTML : si on tronque du texte HTML à une position donnée, il est possible qu'on coupe en plein milieu d'une balise ou d'un caractère écrit sous la forme « &nom; ». Exemple : « J'ai mangé ! » tronqué au 12e caractère donne « J'ai mang&ea ».

Exemple de vulnérabilité : WordPress Charset SQL Injection Vulnerability (décembre 2007). Le problème apparait lorsque la base de donnée utilise un jeu de caractère chinois : Big5 ou GBK. La fonction qui échappe les chaînes de caractères SQL utilise addslashes() qui travaille sur des octets et non pas des caractères. La séquence d'octets (0xB3, 0x27) est alors échappée en (0xB3, 0x5C, 0x27). Or 0xB35C est un caractère valide en Big5, et on obtient donc une apostrophe seule !

Exemple avec Python 2.5 :

>>> user='\xB3\x27'
>>> sql=user.replace("'", "\\'")
>>> print unicode(sql, "big5")
許'

Le problème de fond est que PHP ne supporte pas Unicode. Il va falloir attendre PHP6 qui est en cours de gestation. Notez que ce genre de bug touche également les programmes écrit en C, Java ou même en Python. Bien que Python propose le type unicode, il est rarement utilisé bien que complet. Le module re (expression régulières) supporte les expressions unicode. Python 3000 vise, entre autre, à encourager l'adoption d'Unicode comme type par défaut des chaînes de caractères.

Autres bugs des implémentations d'Unicode

La bibliothèque Qt de Trolltech calculait mal la longueur des chaînes UTF-8 (il manquait un "+1") : Bugzilla Bug 269001: CVE-2007-4137 QT off by one buffer overflow (rapport de bug avec patchs pour Qt3 et Qt4, août 2007).

La fonction repr() du langage Python n'allouait pas assez de mémoire pour les chaînes Unicode : buffer overrun in repr() for unicode strings (août 2006, lire aussi le CVE-2006-4980). La fonction repr() n'allouait que 6 octets par caractère en ne considérant que la forme « \uXXXX », or la forme « \Uxxxxxxxx » peut être nécessaire et consomme 10 octets par caractère.

Conclusion

Unicode regorge de fonctionnalités qui sont souvent méconnues. Mal utilisées ou utilisées à mauvais escient, ça peut faire très mal. Je pense qu'il manque des fonctionalités de sécurité dans les bibliothèques Unicode. Les encodages non standards doivent être rejettés ou une alerte doit être déclanchée. Le module mod_security d'Apache propose ce genre de fonctionnalité : voir SecFilterCheckUnicodeEncoding et @validateUtf8Encoding. Il faudrait pouvoir désactiver toutes les fonctionalités Unicode et n'activer que ce dont on n'a besoin pour éviter les effets de bord indésirables.

vendredi 18 janvier 2008

Analyser un fichier binaire ou un programme inconnu

Cet article explique comment analyser un fichier d'origine peu sûre (ex: Internet) ou dont le format est inconnu (rétro-ingénierie). Il n'est sûrement pas exhaustif, mais liste divers outils bien pratiques pour ce genre de travail.

Avertissement

Lorsqu'on traite un fichier d'origine inconnue, il faut être sur ses gardes. Il se peut que le fichier attaque volontairement les outils d'analyse cités dans cet article. Les virus sont connus pour cracher les débogueurs et/ou changer leur propre comportement lorsqu'ils sont analysés. Travaillez sur une machine dédiée aux tests (ex: machine virtuelle), ou bien avec des privilèges minimaux (ex: machine coupée du réseau).

Détecter le type d'un fichier inconnu

Quand on reçoit un fichier binaire d'un type inconnu, le programme le plus utile est file. Il détermine le format du fichier à partir d'une importante base de signature. Il sait extraire certaines méta-données (dimension d'une image, version du format, etc.) et sait également faire la différence entre les sous-formats (tel que AVI ou WAVE pour le format RIFF, et Theora et Vorbis pour Ogg).

D'autres programmes peuvent servir pour identifier le format ou plutôt extraire les méta-données :

  • hachoir-metadata : supporte un grand nombre de formats de fichiers
  • extract : supporte un grand nombre de formats de fichiers
  • Kaa : images, sons et vidéos
  • identify : images, fait parti de l'excellente suite Image Magick

Analyse manuelle d'un fichier binaire

Le programme strings sert à extraire des chaînes de caractères d'un fichier binaire. Il vous faudra peut-être tester différentes options (encodages des chaînes) pour obtenir satisfaction. Souvent, strings donne beaucoup de faux positifs (la sortie est assez bruitée).

Un éditeur hexadécimal est toujours pratique pour rechercher visuellement des motifs, des chaînes de caractères, informations cachées, etc. J'utilise « hexdump -C fichier » ou bien khexedit (programme KDE).

Quand un fichier semble vraiment trop aléatoire, il se peut qu'il soit compressé et/ou chiffré. J'ai écrit un petit script « entropy.py » qui calcule l'entropie des symboles (mot de 8 bits) d'un fichier. Quelques exemples :

  • Programme EXE PE : 4,11 bits/symbole
  • Page HTML : 4,89 bits/symbole
  • Document PDF : 7,75 bits/symbole
  • Image JPEG et PNG : 7,87 et 7,82 bits/symbole
  • Archive gzip (.tar.gz) et bzip2 (.tar.bz2) : 7,99 bits/symbole

Au delà de 7,5 bits/symbole, il y a de fortes chances que le fichier contienne des champs compressés. C'est le cas dans les exemples, mais cet outil n'est qu'une mesure empirique.

Pour trouver les blocs compressés, une solution est de tenter de décompresser à partir du 1er octet, puis du 2e, etc. Le script « find_deflate.py » implémente justement cet algorithme, lent mais il fonctionne.

Enfin, l'outil hachoir-subfile permet de rechercher les fichiers contenu dans un fichier binaire en recherchant des motifs (marqueur de début, marqueur de fin) et en vérifiant que le fichier trouvé est valide (pour limiter les faux positifs). Il existe beaucoup d'outils similaires tels que Photorec et Scalpel, ou encore TestDisk et Sleuth Kit qui sont eux dédiés à l'analyse d'images de disque dur.

Analyse statique d'un programme

Ayant majoritairement travaillé sous Linux, je ne parlerai que des programmes ELF. L'outil objdump affiche de nombreuses informations sur un fichier ELF tel que ses sections, les symboles (objdump -T fichier) et sait désassembler du code. L'outil nm liste les symboles des bibliothèques statiques (extension du fichier « .a »). L'outil ldd liste les bibliothèques importés par un programme ou une bibliothèque avec le chemin complet qui sera utilisé. Enfin, elfsh est une suite complète d'outils pour l'analyse de fichier ELF.

Analyse dynamique

Analyser un programme sans l'exécuter ne permet que d'extraire de maigres informations. Il est toujours plus instructif de l'exécuter. Il existe de nombreux outils pour tracer un programme. strace affiche les appels systèmes. ltrace affiche les appels aux bibliothèques dynamiques, mais sait également tracer les appels systèmes. gdb est le grand classique parmis les débogueurs, boîte à tout faire.

Pendant l'exécution du programme, « lsof -p pid » affiche les fichiers qu'il a ouvert et « netstat » permet d'afficher les connexions réseaux.

Autres outils que je n'ai pas testé :

J'ai écrit un binding Python pour ptrace qui permet d'écrire facilement son propre outil d'audit à la manière de strace ou ltrace. Enfin, mon bref article Syscall contient mes divers notes sur les appels systèmes Linux.

Sites Internet

Pour en savoir plus sur le sujet, voici quelques sites très instructifs :