07
juin
'12

La difficile publication de vulnérabilité

Publié le 07 juin 2012

La philosophie du hacking implique une curiosité constante, et donc le fait que l'on mette notre nez un peu partout. C'est grâce à cela que des problèmes voire même des vulnérabilités sont identifiés, et résolus. Le problème, car il y a problème, réside dans la difficile communication et le fait que l'on fasse une très bonne cible en faisant part d'une trouvaille.

La curiosité est un vilain défaut

Tout le monde le sait. C'est pas bien. Mais il est des personnes qui ne peuvent s'empêcher de se poser des questions, légitimes ou non. Ce n'est pas (encore) une maladie, mais c'est ce qui motive certaines personnes. Dont moi. Et je dois avouer que la plupart du temps, c'est assez payant. Cette curiosité est le moteur essentiel de ce blog, de mes outils, etc ... Tout vient de questions que n'importe qui (enfin, je suppose que n'importe qui se les pose) pourrait se poser, et de la réflexion qui s'ensuit.

Malheureusement (sic), cette curiosité pousse des fois à tester ou vérifier des faits, à la limite de la légalité. Ce qui permet de cerner correctement un problème et de le reporter ou d'alerter. Et c'est lorsqu'on alerte, que l'on prévient, que les choses se corsent. Car dans certains cas de figure, la question d'où part une analyse ou une réflexion peut être considérée comme inopportune voire comme une action offensive. Alors qu'il s'agissait de pure curiosité, sans animosité aucune. Mais certaines personnes ne voient pas cela d'un bon œil, car le fait d'alerter ou de reporter peut nuire à leur image (ou celle de leur entreprise, ou administration) et je comprends tout à fait le besoin de se couvrir. Il est vrai qu'en France on a cette aversion naturelle contre les hackers (favorisée par les médias), et d'autant plus pour ceux qui publient des choses qui fâchent ou ennuient.

La nécessité de transparence ?

Il est évident que la publication, la diffusion, le fait d'alerter sur une vulnérabilité ou un problème identifié ne doit pas être fait n'importe comment. Certains préfèrent l'anonymat, et dévoilent à coup de pastebin des fuites d'informations, ou des vulnérabilités originales. S'ensuit généralement une tentative d'identification de la source, qui des fois aboutit après de fastidieuses recherches. Mais le fait de publier sous couvert d'anonymat vous place directement dans la mauvaise catégorie: pourquoi vous cachez-vous, si vous n'avez rien à vous reprocher ?

A contrario, beaucoup d'entre-nous pensent que la transparence est le meilleur atout. Nous nous permettons de publier des alertes, de reporter des problèmes ou des erreurs en toute bonne foi, sans nous cacher forcément derrière un pseudonyme (bien qu'on puisse en avoir un) et revendiquer la trouvaille. Si jamais quelqu'un est froissé, ou ennuyé par une publication, l'auteur de celle-ci est identifiable et joignable. De fait, annoncer sur Twitter ou par email sans masquer son identité est gage de sérieux, ou de grande folie. Malheureusement, lorsque les choses se gâtent, la seconde option est préférée.

Heuu ... à quand une prise de conscience ?

De suite, on tape sur la personne qui a identifié le problème. Vous annoncez sur Twitter que vous avez identifié une vulnérabilité dans un produit sans donner trop de détails ? Vous dévoilez un fichier planqué au fin fond du web et contenant des informations critiques ? Vous avez identifié un problème ou une anomalie et communiquez dessus au monde entier ? You're doing it wrong. Et même si l'envie vous prend de le transmettre par email, l'issue est globalement la même: l'annonceur est fautif. Victime de sa curiosité. Et peut-être de sa volonté d'alerter et de faire connaître sa trouvaille. On tire sur le messager, meme si je dois avouer que dans certains cas il n'est pas forcément tout blanc.

La problématique est ancienne: comment dévoiler une vulnérabilité/anomalie/faille en toute bonne foi, sans se cacher, et sans risquer les foudres des personnes concernées ? Quid du responsible disclosure ? C'est un débat qui dure, bien qu'en France il soit déjà plié, j'en ai bien peur. Par expérience, j'ai tendance à dire que lorsque l'on tente de dévoiler ou d'alerter, on devient une cible (que cela soit justifié ou non, là n'est pas la question). Certes, nous pronons le hacking "éthique", et cette volonté de transparence et de communication fait partie de cette éthique. Mais sincèrement, il est plus risqué de communiquer sur une vulnérabilité que de se taire et de la garder pour soi. Beaucoup en ont fait l'expérience: Guillermito [1], Damien Bancal [2] par exemple.

Alors, comment dévoiler/communiquer ?

Le constat est triste: soit on dévoile et on risque les sanctions prévues aux différents articles de la loi française (pour rappel, les articles 323-1 à 323-7 [3], avec des peines allant de 2 ans d'emprisonnement et 30 000€ d'amende à 5 ans d'emprisonnement et 75 000€ d'amende), bien que de bonne foi, soit on se tait et les choses ne bougent pas. J'avoue que cette vision est pessimiste, mais sincèrement je ne vois pas comment on pourrait éviter cela. Triste constat, disais-je.

Allons-nous devoir attendre que les responsables sécurité arrêtent de faire la sourde oreille et tentent de sauver leur place sans assumer les problèmes qui se posent ? Faut-il encore diaboliser Internet, l'informatique et le hacking en général, ainsi que les personnes compétentes en France ? L'ANSSI a ouvert la voie (du moins on ose le croire), en faisant appel à la culture hacker (il n'y a qu'à voir leur wallpaper `4]), en recrutant massivement et en le faisant savoir à différentes conférences sécurité prisées des hackers (non, pas de troll sur l'AN^W^WSSTIC). A qui le tour ?

Références

[1] [L'affaire Guillermito <http://guillermito2.net/archives/2004_12_28.html>`_

`2] [Damien Bancal (Zataz) vs le FTP anonyme <http://www.pcinpact.com/news/48753-zataz-faille-securite-trou-signalement.htm>`_

`3] [Code pénal, Livre III, Titre III, Chapitre III: Des atteintes aux systèmes de traitement automatisés de données <http://www.legifrance.gouv.fr/affichCode.do;jsessionid=DBA449F582FD48DF11068D44A409B79B.tpdjo07v_3?idSectionTA=LEGISCTA000006149839&cidTexte=LEGITEXT000006070719&dateTexte=20120510>`_

`4] [Wallpaper ANSSI <http://www.ssi.gouv.fr/IMG/png/wallpaper-anssi-2560x1920.png>`_

09
avril
'12

Bulletproof JPEGs

Publié le 09 avril 2012

Certainement que, comme moi un de ces jours où l'on a le cerveau patraque, vous vous êtes retrouvés face à une vulnérabilité permettant une inclusion de fichier local sans avoir identifié de moyen d'envoyer (ou de créer) sur le serveur distant un fichier contenant du code actif... Et ce n'est pas faute d'avoir essayé d'injecter dans le fichier de session, voire même dans les logs ou les images uploadées via un formulaire ! Mais rien n'y fait. Même ce satané formulaire d'upload écrit en PHP et utilisant la bibliothèque GD pour décoder et écrire ensuite sur disque les images qui lui sont envoyées nous empêche d'obtenir un remote shell. Damned, we're doomed.

Quelques fuites d'informations

C'est vrai que d'habitude, l'envoi d'une image au format JPEG par exemple avec un commentaire paramétré avec Gimp fait l'affaire, en dernier recours. Seulement dans mon cas, ce n'était pas possible, la bibliothèque GD étant utilisée pour décoder et écrire ensuite le fichier image sur disque: celle-ci a la fâcheuse manie de remplacer tout commentaire existant et d'y coller le sien:

Pour le coup, cette particularité est très intéressante: la qualité utilisée pour stocker l'image est dévoilée, en l'occurrence il s'agit d'une qualité de 99 (celle par défaut étant de 75 environ). On sait aussi que l'image a bien été générée par la bibliothèque GD. Bon, c'est pas folichon, mais ca peut aider.

Découverte du format de fichier JPEG

Le site distant n'acceptant que les images au format JPEG, j'ai donc décidé de mettre les mains dans le cambouis, et de voir comment est structuré ce format de fichier. L'objectif principal étant d'arriver à trouver une zone dans laquelle on peut écrire mais qui n'est pas supprimée par cette satanée bibliothèque. Je suis donc parti à la chasse aux sources, et j'en ai trouvé plusieurs:

Le format de fichier JPEG (Joint Photographic Expert Group) est un format de stockage d'image qui emploie une compression avec perte, basée sur une conversion d'une disposition de pixels en répartition fréquentielle (grosso modo). Le problème de ce format de fichier, c'est que la configuration des pixels composant l'image n'est pas écrite telle quelle dans le fichier, contrairement aux images dites raster (comme le format BMP de Microsoft, ou le format GIF), et cela pose des soucis. En particulier quand on cherche à insérer un bout de code PHP pour exploiter convenablement une LFI.

De plus, le format de fichier est assez particulier, l'ensemble des informations étant réparties dans des sections, définies par des marqueurs. Un marqueur débute toujours par l'octet de valeur 255 (0xFF), et contient un code indiquant son rôle. Ainsi, le marqueur ayant pour code 0xD8 marque le début de l'image (Start of Image, ou SOI), et celui ayant pour code 0xD9 la fin de l'image (EOI). D'autres marqueurs sont aussi définis, dont celui définissant un commentaire, 0xFE. Je vous renvoie aux quelques liens donnés précédemment pour de plus amples informations.

Mais alors, où écrire dans ce format de fichier ?

C'est toute la question. Il ne peut y avoir qu'un champ de commentaire dans un fichier JPEG, et de toute façon nous avons vu que celui-ci était écrasé par la bibliothèque GD lors de la sauvegarde. Le format de fichier JPEG autorise aussi des marqueurs spécifiques aux applications (les fameux APPX), mais encore une fois ceux-ci ne sont pas pris en compte par GD. Il ne nous reste pas d'autre choix que de tenter une insertion dans les données stockées dans le fichier, servant à la reconstruction de l'image.

En théorie, il suffit de localiser la section SOS (Start of Scan, ayant pour marqueur 0xDA), de trouver les données compressées qui la suivent et de remplacer les premiers octets avec notre payload PHP. Oui, en théorie c'est censé fonctionner. Seulement en pratique, notre payload PHP va être interprété comme une donnée compressée et servir ensuite à générer une image composée de pixels que nous ne maîtrisons pas. Une fois recompressée par la bibliothèque GD, rien ne nous garantit que notre payload PHP sera conservé. L'insertion idéale consisterait à injecter à la place des données permettant de reconstituer les pixels notre payload PHP, et lorsque GD décode puis encode l'image, que notre payload soit conservé et écrit sur disque. Dans le cas où le fichier ne transite pas par la bibliothèque GD, celui-ci contient tout de même notre payload et fonctionnera. Ainsi, nous serions à même de construire une image Jpeg contenant du code PHP malveillant, et résistante aux transformations induites par la compression réalisée par la bibliothèque GD !

Création d'une image Jpeg "bulletproof"

Pour pouvoir créer ces merveilleuses images, j'ai tout d'abord codé en Python le code réalisant l'injection. Pour effectuer celle-ci, il suffit de rechercher la séquence d'octet 0xFF 0xDA (correspondant à la section Start of Scan), puis de lire les deux octets qui suivent (contenant la taille de la section stockée sur 2 octets en big-endian), afin de trouver l'endroit où les données compressées sont écrites. On recherche ensuite à partir de cet emplacement le marqueur de fin d'image (0xFF 0xD9), et les données situées entre les deux correspondent aux données compressées définissant le contenu de l'image (enfin, une partie pour être précis, mais là n'est pas la question).

Il est ensuite trivial de remplacer les quelques octets de début par notre payload. Notez que dans le code présent j'ai prévu un décalage variable, j'y reviendrai plus tard. Voici le code de cette fonction:

def insertPayload(_in, _out, payload,off):
    img = _in
    # look for 'FF DA' (SOS)
    sos = img.index("\xFF\xDA")
    sos_size = struct.unpack('>H',img[sos+2:sos+4])[0]
    sod = sos_size+2
    # look for 'FF D9' (EOI)
    eoi = img[sod:].index("\xFF\xD9")
    # enough size ?
    if (eoi - sod - off)>=len(payload):
            _out.write(img[:sod+sos+off]+payload+img[sod+sos+len(payload)+off:])
            return True
    else:
            return False

Pour tester les images générées, j'ai installé les bindings Python de la bibliothèque GD, sous debian le package nommé python-gd. Ces bindings permettent de simuler à l'aide de Python l'ensemble des traitements effectués par la bibliothèque GD, et en particulier de reproduire ce qu'il se passe sur le serveur cible à savoir l'ouverture puis l'écriture sur disque de l'image uploadée.

Un point est cependant capital à prendre en compte: la qualité de l'image. GD permet de définir une qualité (tout comme plein d'autres logiciels d'infographie, comme GIMP par exemple) afin d'ajuster la taille du fichier sur disque. Plus la qualité est bonne (proche ou égale à 100), plus le fichier sera gros et l'image nette, et a contrario plus celle-ci est faible plus le fichier sera petit et l'image dégradée. Ce facteur de qualité est très important: il faut utiliser exactement le même facteur lors de la génération de l'image bulletproof afin d'être sûr que le serveur distant va bien générer notre payload lors de l'écriture sur disque. La fuite d'information découverte précédemment va sûrement vous être utile afin de déterminer la qualité employée =).

J'ai automatisé la génération des images bulletproof à l'aide d'un script Python (encore un), et j'ai ainsi pu générer des images contenant le code PHP (ou équivalent) suivant pour les facteurs de qualité de 52 à 98:

<?php system($_GET['c']); ?>

Certes, quelques variantes ont du être employées pour assurer une insertion maximale, mais le résultat est plutôt intéressant. De plus, mon script essaie d'insérer le payload à différents endroits, pas forcément au début de la section (vous vous rappelez de l'offset dans la fonction d'insertion ?), car les phases de décompression/compression rendent le résultat un poil aléatoire. Ainsi, voici à quoi ressemble une image avant traitement par la bibliothèque GD (côté serveur), image contenant notre payload PHP (pour la qualité par défaut):

En regardant en détail le contenu de l'image, on peut apercevoir le payload PHP:

Fin mot de l'histoire

Avec cette image JPEG, j'ai pu facilement contourner la restriction imposée via la bibliothèque GD et forcer celle-ci à écrire elle-même une image contenant un code PHP malveillant, qui m'a permis d'exécuter des commandes systèmes sur le serveur distant à l'aide de la vulnérabilité d'inclusion de fichier local trouvée auparavant.

Pour vous éviter de générer tout vous-même, je vous ai préparé une petite archive contenant mon code python ayant servi à la génération de toutes les images bulletproof, ainsi que les images elles-même (32x32 pixels). Si avec ça je ne vous gâte pas pour Pâques, je ne comprends pas ...

Pour terminer sur une note sécuritaire, lorsque vous autorisez l'upload d'images au format JPEG (mais ceci est d'ailleurs vrai avec d'autres formats comme BMP ou PNG, une attaque identique pouvant être réalisée) prenez plusieurs précautions:

version de GD employée: 2.0.36rc1

version de Python employée: 2.7

19
mars
'12

Vodstok, un outil de partage volontaire

Publié le 19 mars 2012

Durant le meeting du mois de Janvier, j'avais présenté un de mes outils nommé Vodstok (pour Voluntary Distributed Storage Kit), mais il n'était pas finalisé. J'ai pris un peu de temps parmi le peu disponible pour tenter de finaliser le code et pouvoir le mettre à disposition de tous. Cet outil permet à tout un chacun de pouvoir uploader et télécharger des fichier stockés sur Internet, dans un nuage de serveurs de stockage mis à disposition par des volontaires.

Stockage distribué volontaire

Des personnes utilisant Vodstok l'installent sur leurs serveurs web (aucune base de données requise, compatible PHP4/5) et décident ainsi de partager une portion de leur espace de stockage aux utilisateurs de Vodstok. Chaque propriétaire de serveur peut ainsi s'ajouter à un réseau maillé de serveurs, et l'ensemble constitue une plate-forme de stockage distribué accessible en HTTP.

La force de ce système est d'une part sa résistance aux coupures: si un serveur tombe, le système est toujours fonctionnel, bien que les données du serveur perdu soient considérées comme perdues (Vodstok n'assure pas de redondance actuellement). Mais il est toujours possible de s'en servir, et en comptant sur l'effet Streisand, cela devrait a priori favoriser ce système. D'autre part, Vodstok se base sur le protocole HTTP, qui est universel et permet même de passer au travers de proxies, ce qui évite d'avoir une configuration de ports particulière, comme c'est le cas avec BitTorrent par exemple.

Vodstok intègre un client en ligne de commande écrit en Python, qui permet d'assurer un stockage sécurisé (AES256) et une répartition d'un fichier sur l'ensemble des serveurs. Bien sûr ce client peut être modifié et les usages dérivés sont nombreux, la seule limite reste votre imagination. Pour faire simple, un serveur sur lequel vodstok est déployé fait office de système de stockage seul, il n'y a pas d'autre logique particulière. On peut le considérer comme un "bout de disque dur" qui ne regarde pas ce qui y est stocké ni comment s'en servent les utilisateurs.

Comment partager une partie de son espace de stockage web avec Vodstok

En premier lieu, il faut récupérer l'archive de Vodstok sur la page du projet, et copier le contenu du dossier www sur le serveur distant. Une rapide modification du fichier de configuration config.inc.php permet de paramétrer le bouzin:

<?php

define('QUOTA_MB', 1024); /* Je décide de partager 1Go d'espace disque */
define('MAX_ENDPOINTS',1500);
define('MAX_ENDPOINT_LEN',200);

/*
DO NOT MODIFY OR REMOVE THE FOLLOWING LINES
*/

define('QUOTA',QUOTA_MB*1024*1024);
define('CHUNK_DIR','chunks');
define('ENDPOINT_DIR','endpoints');

?>

On chmod 777 les dossiers chunks et endpoints, et c'est tout, Vodstok est paré à fonctionner côté serveur. Reste à voir comment s'en servir côté client.

Pour le client, rien de bien compliqué: le dossier client contient le code source de l'outil, qui est pour l'instant seulement en ligne de commande. On le copie dans un dossier accessible à l'utilisateur, et on peut le lancer:

# chmod +x /opt/vodstok-1.2 && cd /usr/bin/local && ln -s /opt/vodstok-1.2/vodstok vodstok
$ vodstok
Usage: vodstok [options] [VDS URL]

Options:
  -h, --help            show this help message and exit
  -l, --list-endpoints  List saved endpoints
  -a ENDPOINT, --add-endpoint=ENDPOINT
                        Add an existing endpoint
  -r ENDPOINT, --del-endpoint=ENDPOINT
                        Remove an existing endpoint
  -p ENDPOINT, --publish=ENDPOINT
                        Publish an existing endpoint
  -w DIRECTORY, --write=DIRECTORY
                        Set destination directory
  -s, --size            Displays distributed storage capacity
  -v, --version         Show version
  -u, --update          Update endpoints

Première étape, ajouter le point de stockage web nouvellement créé, ainsi que d'autres déjà existant:

$ vodstok -a http://www.monserveur.com/vodstok/
$ vodstok -a http://virtualabs.fr/vodstok/
$ vodstok -a http://www.depotware-network.net/vodstock/
$ vodstok -a http://vodstok.zengeek.org/

Lors de chaque ajout de point de stockage (ou endpoint), vodstok vérifie que celui-ci est bien fonctionnel.

Seconde étape, publier votre point de stockage:

$ vodstok -p http://www.monserveur.com/vodstok/

Etape ultime, récupérer de nouveaux points de stockage grâce à la fonctionnalité de mise-à-jour intégrée:

$ vodstok -u

Comment échanger des fichiers grâce à Vodstok

Pour échanger des fichiers, vous n'avez pas obligatoirement besoin d'installer un point de partage (mais ça serait quand même 'achement bien, pour la bonne santé et l'espace de stockage du système). Il est tout de même conseillé d'ajouter au moins un point de stockage existant, et de mettre à jour la liste des points de stockage:

$ vodstok -a http://virtualabs.fr/vodstok/
$ vodstok -u

Une fois cela fait, il est alors très facile d'uploader un fichier sur le réseau vodstok:

$ vodstok britney-spears-chante-sous-la-douche.mp3
[+] Uploading ... 100.00%
[i] File successfully uploaded.
Share this link: vds://054B22B4D64746A94CCE72AB410516C7@www.depotware-network.net/vodstock/#5e23d232a24a332d3e64f02d9b9c9350

Et c'est identique pour le téléchargement:

$ vodstok vds://9AAFC5E52E29E833D969399E9EEF3D0F@virtualabs.fr/vodstok/#6edc5a91f3c29190229822f0119f9745
[+] Retrieving file summary
[+] Starting file download
[+] Downloading ... 100.00%
[i] File saved as britney-spears-chante-sous-la-douche.mp3

Source et contribution

J'ai mis le projet sur [GITHUB], et monté un repository Vodstok sur mon hébergement. N'hésitez pas à tester, jeter un oeil, et si vous pensez pouvoir apporter votre brique, à modifier le code et proposer vos améliorations.

Conclusion

Le projet vodstok est actuellement viable, mais toujours en phase beta. Je suis en train d'essayer d'améliorer le système et de faire en sorte que celui-ci soit fiable et efficace. Essayez-le, installez des points de stockage, et faites moi vos retours d'expérience par email (virtualabs -at- gmail -dot- com) ou directement via twitter !

`GITHUB] [https://github.com/virtualabs/Vodstok <https://github.com/virtualabs/Vodstok>`_ `HOMEPAGE] [http://virtualabs.fr/vodstok/ <http://virtualabs.fr/vodstok/>`_



Les contenus disponibles sur ce blog sont publiés sous licence Creative Commons BY-NC-SA.
Vous pouvez réutiliser tout ou partie de ces contenus à condition de citer l'auteur et l'origine, vous ne pouvez en faire une utilisation commerciale, et enfin vous devez partager tout travail ou œuvre dérivée sous les mêmes conditions — c'est-à-dire avec la même licence d'utilisation Creative Commons.