TrueGalerie 1.0 (PHP) Admin Access & File Copy
|
Date de Publication: 2003-04-25
Titre: TrueGalerie 1.0 Admin Access & File Copy
K-Otik ID : 0036
Exploitable à distance : Oui
Exploitable en local : Oui
* Description Technique - Exploit *
TrueGalerie est un "script de gestion de galerie avec une zone d'administration qui vous permer d'ajouter, editer ou supprimer des catégorie ainsi que les images non désirées".
Deux fichiers peuvent servir pour l'authentification admins : verif_admin.php et check_admin.php.
Ils sont d'ailleurs identiques, et voici leur code source :
---------------------------------------------------------------------------------------
<?
if(isset($connect)) {
if($connect=="$passadmin") setcookie("loggedin","ok");
if($connect=="no") setcookie("loggedin");
Header("Location: ".$PHP_SELF);
}
$ok = ($loggedin!="");
if($ok) {
echo "<center>";
echo "<table>";
echo "<tr><td align='center'><a
href='?connect=no'>DECONNEXION</a></td></tr>";
echo "</table>";
echo "</center>";
}
else {
echo "<center><form method='post'>";
echo "<table>";
echo "<tr><td align='center'>CONNEXION</td></tr>";
echo "<tr><td align='center'>Password : admin</td></tr>";
echo "<tr><td><input type='password' name='connect'></td></tr>";
echo "<tr><td><input type='submit' value='Login'></td></tr>";
echo "</table>";
echo "</form></center>";
}
?>
---------------------------------------------------------------------------------------------------
Si la variable $connect n'est pas vide, on vérifie si elle est égale au mot de passe admin, $passadmin (stocké dansle fichier config.php, avec les informations de la DB). Si c'est le cas, on envois un cookie avec comme nom loggedin et comme valeur ok. Si $connect vaut "no", on vide le cookie.
Ensuite, on vérifie si $loggedin est vide ou pas, et si il ne l'est pas, on est considéré comme admin. Le problème est qu'on est pas obligé de passer par la partie gérant la variable $connect. On peut donc nous même donner une valeur à $loggedin pour s'authentifier admin !
Donc pour être administrateur, il suffira d'entrer une url du type :http://[target]/admin.php?loggedin=1
TrueGalerie permet aussi d'ajouter des images, donc de les uploader sur le site.
Le formulaire pour envoyer une image se trouve dans form.php :
-----------------------------------------------------------------------
[...]
<form method="post" action="upload.php" ENCTYPE="multipart/form-data">
[...]
<input type="text" class="text"name="pseudo">
[...]
<input type="text" class="text"name="email">
[...]
<input type="text" name="url" value="http://">
[...]
<input type="text" name="message">
[...]
<input name="file" type="file">
<input type="hidden" name="MAX_FILE_SIZE" value="<? echo $max_size;
?>">
[...]
<input type="submit" class="bouton" value="valider">
<? echo "<input type='hidden' name='cat_id' value='$catid'>"; ?>
[...]
</form>
[...]
-----------------------------------------------------------------------
Première petite faille, on peut nous-même définir la valeur maximal de la taille du fichier. Pour cela il suffit de copier le formulaire, et de remplacer la taille à la ligne :
<input type="hidden" name="MAX_FILE_SIZE" value="[TAILLE]">
Le formulaire renvois au fichier upload.php dont voici le code :
-----------------------------------------------------------------------
[...]
$userip = $REMOTE_ADDR;
$pseudo = $_POST['pseudo'];
$message = $_POST['message'];
$email = $_POST['email'];
[...]
if((!$pseudo) || (!$message) || (!$file)) {
[...]
exit;
}
if(!ereg('^[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+'.
'@'.
'[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.'.
'[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$',
$email))
{
[...]
exit();
}
[...]
if ($file_size >= $MAX_FILE_SIZE)
{
[...]
exit();
}
if($HTTP_POST_FILES['file']['type']=="image/pjpeg") {
$ext="jpg";
}
elseif($HTTP_POST_FILES['file']['type']=="image/gif") {
$ext="gif";
}
if($HTTP_POST_FILES['file']['type']=="image/pjpeg"|$HTTP_POST_FILES['file']['type']=="image/gif")
{
$date = time();
$query = "INSERT INTO $tablegalerie
(cat_id,pseudo,email,url,message,date,clicks,img,userip)
VALUES('$cat_id','$pseudo','$email','$url','$message','$date','','','$userip')";
mysql_query($query);
$id=mysql_insert_id();
$random_name = makeRandomName();
$dest_file="./$folder/$random_name.$ext";
$query = "UPDATE $tablegalerie SET img='$dest_file' WHERE id='$id'";
mysql_query($query);
$res_copy=@copy($file,$dest_file);
@move_uploaded_file($file,$dest_file);
-----------------------------------------------------------------------------
Il y a plusieurs restrictions avant le code qui nous interesse :
----------------------------------
$res_copy=@copy($file,$dest_file);
----------------------------------
D'abord les variables $pseudo, $message et $file ne doivent pas être
vide.
$file est bien sûr la variable dans laquelle se trouve le chemin du
fichier
à copier.
Ensuite $email doit respecter la syntaxe d'un email, c'est-à-dire
[quelquechose]@[quelquechose].[quelquechose].
Puis le fichier ne doit pas être plus gros que la taille maximale
imposée
(ce qui ne pose pas de problème puisqu'on
peut la choisir nous-même).
Enfin le fichier envoyé par le formulaire POST (ce qui n'est pas sans
importance) doit être de type jpg ou gif, comme
le montrent ces lignes :
------------------------------------------------------------------------------
if($HTTP_POST_FILES['file']['type']=="image/pjpeg") {
$ext="jpg";
}
elseif($HTTP_POST_FILES['file']['type']=="image/gif") {
$ext="gif";
}
if($HTTP_POST_FILES['file']['type']=="image/pjpeg"|$HTTP_POST_FILES['file']['type']=="image/gif")
{
------------------------------------------------------------------------------
On voit donc qu'il est bien définit que c'est une variable de type POST
($HTTP_POST_FILES['file']).
Le problème vient du fait que à la ligne
$res_copy=@copy($file,$dest_file);,
le type de la variable n'est pas
définit !
En effet, PHP a comme propriété ceci :
Imaginons un fichier fichier.php contenant le code suivant :
------------------------------------------------------------------------------
<?
setcookie("variable","cookie");
echo "<form method=POST action=fichier.php><input name=variable
value=post><input type=submit></form><br>";
echo $variable;
?>
------------------------------------------------------------------------------
La variable $variable est définie deux fois :
- Une fois par cookie, on lui donne la valeur "cookie"
- Une autre fois par formulaire POST, on lui donne la valeur "post"
Si on envois le formulaire, la ligne echo $variable; va avoir a choisir entre la valeur donnée par cookie et la valeur donnée par
POST, problème qu'il n'y aurait pas eu si on avait définit :
echo $HTTP_POST_VARS["variable"];
ou
echo $HTTP_COOKIE_VARS["variable"];
Et bien PHP choisira toujours par défaut la valeur donnée par cookie, donc ici la ligne de code affichera la string "cookie".
On peut donc, sur TrueGalerie, décider soi-même quel fichier va être copié... et donc lire par exemple les sources du fichier config.php, contenant password admin, password BD, serveur BD, login BD,...
Pour se faire, voilà la marche à suivre :
- Envoyer un cookie sur http://[target]/form.php avec comme nom "file"
et
comme valeur "config.php"
- Depuis cette page, remplir normalement le formulaire, y compris pour l'image, qui doit être du type gif ou jpg
- Envoyer le formulaire
- Se rendre sur l'index, dans la liste des images, et regarder la dernière uploadée, la vôtre, qui sera de l'extension de l'image entrée dans le formulaire
- Copier l'url et s'y rendre via son webbroser, pour pouvoir lire en clair les sources de config.php
* Versions Vulnérables *
TrueGalerie 1.0
* Solution *
Un patch est disponible sur http://www.phpsecure.info.
- Dans admin.php, ajouter avant les inclusions la ligne :
session_start();
et dans verif_admin.php et check_admin.php, remplacer les lignes :
----------------------------------------------------------------
if(isset($connect)) {
if($connect=="$passadmin") setcookie("loggedin","ok");
if($connect=="no") setcookie("loggedin");
Header("Location: ".$PHP_SELF);
}
$ok = ($loggedin!="");
----------------------------------------------------------------
par :
-------------------------------------------------------------------
if(isset($connect)) {
if($connect=="$passadmin") {
$loggedin = "ok";
session_register("loggedin");
}
if($connect=="no") session_unregister("loggedin");
Header("Location: ".$PHP_SELF);
}
$ok = session_is_registered("loggedin");
-------------------------------------------------------------------
- Dans form.php, supprimer :
<input type="hidden" name="MAX_FILE_SIZE" value="<? echo $max_size;
?>">
et dans upload.php, remplacer :
---------------------------------
if ($file_size >= $MAX_FILE_SIZE)
---------------------------------
par
----------------------------
if ($file_size >= $max_size)
----------------------------
- Remplacer dans upload.php les lignes
--------------------------------------
$res_copy=@copy($file,$dest_file);
@move_uploaded_file($file,$dest_file);
--------------------------------------
par
---------------------------------------------------------
$res_copy=@copy($HTTP_POST_FILES['file'],$dest_file);
@move_uploaded_file($HTTP_POST_FILES['file'],$dest_file);
---------------------------------------------------------
* Crédits *
Faille découverte par frog-m@n et l'équipe de PhpSecure (Avril 2003).
|