COMMAND
php
SYSTEMS AFFECTED
_Almost_ any PHP program which provides file upload capability
PROBLEM
Following is based on a Secure Reality Pty Security Advisory #1.
They found this particular issue a while ago but were planning to
disclose it at a later date once we had a chance to investigate
its imact on most popular PHP software. However, the issue was
recently half found/disclosed by a poster on the php-general
mailing list, who didn't appear to realise its impact.
PHP is a feature heavy web scripting language that has become
widely popular. One of its many features is easy handling of file
uploads from remote browsers. This functionality is very commonly
used, particularly in photo gallery, auction and webmail style
applications.
The way that PHP handles file uploads makes it simple to trick PHP
applications into working on arbitrary files local to the server
rather than files uploaded by the user. This will generally lead
to a remote attacker being able to read any file on the server
that can be read by the user the web server is running as,
typically 'nobody'.
Impact:
1. File disclosure
2. (1) will often lead to disclosure of PHP code
3. (2) will often lead to disclosure of database
authentication data
4. (3) may lead to machine compromise
When files are uploaded to a PHP script, PHP receives the file,
gives it a random name and places it into a configured temporary
directory. The PHP script is given information about the file
that was uploaded in the form of 4 global variables. Presuming
the file field in the form was called 'hello', the 4 variables
would be:
$hello = Name of temporary file (e.g '/tmp/ASHDjkjbs')
$hello_name = Name of file when it was on the remote computer (e.g 'c:\hello.tmp)
$hello_type = Mime type of file (e.g 'text/plain')
$hello_size = Size of uploaded file (e.g 2000 bytes)
The temporary file is automatically deleted at the end of the
execution of the script so the PHP script usually needs to move
it somewhere else. For example, it might copy the file into a
blob in a MySQL database.
The problem is actually in the way PHP behaves by default. Unless
deliberately configured otherwise (via register_globals = Off in
php.ini) the values specified in form fields upon a submit are
auctomatically declared by their form name as global variables
inside the PHP script.
If You had a form with an input field like
<INPUT TYPE="hidden" NAME="test" VALUE="12">
When the PHP script is called to handle the form input, the global
variable $test is set. This is a significant security risk. The
problem is simple, cluttering the global namespace with user
defined input so destablizes the environment that it is almost
impossible to write in it securely.
Back to the issue at hand. Using the fact mentioned above, we can
create the four variables $hell, $hello_name, $hello_type,
$hello_size ourselves using form input like the following
<INPUT TYPE="hidden" NAME="hello" VALUE="/etc/passwd">
<INPUT TYPE="hidden" NAME="hello_name" VALUE="c:\scary.txt">
<INPUT TYPE="hidden" NAME="hello_type" VALUE="text/plain">
<INPUT TYPE="hidden" NAME="hello_size" VALUE="2000">
This should lead the PHP script working on the passwd file,
usually resulting in it being disclosed to the attacker.
Here's how you can reproduce this on your own. Create the
following file as "test.php" on the http server running php:
<!-- test.php ##################################################### -->
<html>
<body>
<form action="<?php echo $PHP_SELF ?>" method="POST"
ENCTYPE="multipart/form-data">
<input type="file" name="userfile">
<input type="submit">
</form>
<pre>
<?php
echo("userfile =$userfile \n");
echo("userfile_name = $userfile_name \n");
echo("userfile_type = $userfile_type \n");
echo("userfile_size = $userfile_size \n");
?>
</pre>
</body>
</html>
<! -- CUT HERE #################################################### -->
Now, create a file on your LOCAL computer called test.html with
the following contents:
<!-- test.html ##################################################### -->
<html>
<body>
<form action="http://YOUR_SERVER_HERE/blah/blah/test.php"
ENCTYPE="multipart/form-data" method="POST">
<input type="file" name="userfile">
<input type="hidden" name="userfile" value="hackme">
<input type="submit">
</form>
</body>
</html>
<! -- CUT HERE #################################################### -->
Go to http://YOUR_SERVER_HERE/blah/blah/test.php and run the
script, upload any file. Note the output. Now open test.html on
your LOCAL computer and repeat the same steps you did when you
were on the server. Hit submit. Note the change in output.
SOLUTION
PHP supports RFC 1867 based file uploads. PHP saves uploaded
files in a temporary directory on the server, using a temporary
name. This temporary name is exposed to the PHP script as $FOO,
where "FOO" is the name of the file input tag in the submitted
form. Many PHP scripts process $FOO without taking measures to
ensure that it is in fact a file that resides in a temporary
directory. It's possible for a remote attacker to supply
arbitrary file names as values for FOO, by submitting a standard
form input tag by that name, and thus cause the PHP script to
process arbitrary files.
Never trust any input that may be coming from the remote user.
Always test whether the variable you expect to contain the path
of an upload file, actually contains a file path of a temporary
file in the system. It is strongly recommended to turn
register_globals off if possible. If register_globals is off, you
can safely check $HTTP_POST_VARS[] for information about the
upload files (see below). If register_globals is kept on, one
must realize that any variable in the global scope might be
overwritten by remote user input.
New versions of PHP have been packaged (4.0.3RC1 and 3.0.17RC1),
to make it easier to secure scripts from this vulnerability.
They include a new function that make it easy to determine
whether a certain filename is a temporary uploaded file or not:
/* Test whether a file is an uploaded file or not */
is_uploaded_file($path);
PHP 4.0.3 also features a new convenience function:
/* Move an uploaded file to a new location. If the file is not
* a valid upload file, no action will take place.
*/
move_uploaded_file($path, $new_path);
In addition, as of PHP 4.0.3, it's safe to use
$HTTP_POST_FILES["FOO"]["tmp_name"] - which cannot be written to
by any remote user input, even when register_globals is on. The
new versions are currently in testing, and thus have the RC tag.
PHP 4.0.3RC1:
http://www.php.net/do_download.php?download_file=php-4.0.3RC1.tar.gz
PHP 3.0.17RC1 (upgrading to PHP 4.0 is strongly recommended):
http://www.php.net/distributions/php-3.0.17RC1.tar.gz