Title:Securing file uploads under PHPContributor: Martin Sarsale (aka runa)
Last Update: Wednesday August 16 15:32:08 EDT 2000
Introduction
------------
Uploads in php are very easy to implement, but they're a little bit
insecure. This is not something with php and it can be fixed in 10 lines, but
you have to be aware. Without this, any file accessible by the HTTPD can be
viewed (read: /etc/passwd, .htpasswd, etc) and it's possible to (in some
situations) overwrite/create files.
Introduction II
---------------
As you may know, when the user uploads the file, PHP will set in
upload.php (the upload processor) 4 variables:
- $newFile
- $newFile_name
- $newFile_size
- $newFile_type
* $newFile is the temp path of the file uploaded. When upload.php finishes, php
will delete the temp file (so, it's a smart idea to copy / move it to a safe
place)
* $newFile_name, size and type are the original name, size and type of file
(image/gif).
The problem is not easy to see without watching the code:
Code of the HTML form:
----------------------
<form action="upload.php" enctype="multipart/form-data" method='post'>
Send: <input type="file" name="newFile">
</form>
Code of the PHP upload processor (upload.php)
---------------------------------------------
Then, upload.php will copy the uploaded file to another dir, let's say
"/apache/images":
<?copy ($newFile,'/apache/images/'.$newFile_name);?>
This script is very simple, but you must have something like this on
your site.
PROBLEM 1 - Reading files
-------------------------
Think about this:
What if, instead of using 'type="file"' you use 'type="text"' and in
the field newName you write '/etc/passwd' ...
In practice:
Point your browser to:
http://www.mysite.com/upload.php?newFile=/etc/passwd&newFile_name=pass.txt
upload.php will copy $newFile ('/etc/passwd') to
'/apache/images/pass.txt' which is accessable by the browser because it's
inside the document root (or it's an alias inside the document root, which is
the same).
One of the possible solutions is to copy the file directly from the
temp dir (hardcoding the location of the temp dir, instead of using $newFile
as the path.
This is, instead of:
copy ($newFile,'/apache/images/'.$newFile_name);
use something like:
<?
function securePath($newFile){
// $secured will be the temp path
$tmp = explode('/',$newFile); // split the pieces of the path in '$tmp'
// get the last piece of the path
// (the name of the temp file)
$tempName = $tmp[count($tmp)-1];
$secured = '/tmp/'.$tempName; // "/tmp/" is the hardcoded temp path
$secured = ereg_replace("\.{2,}","",$secured);
// remove '..'+ from the tempname
// $secured_name will be the
// secured filename
return $secured;
}
function secureFileName($newFile_name){
$secured_name = ereg_replace("\.{2,}","",$newFile_name);
// to avoid '..'+ in the
// file name (more about
// this, later :) )
return $secured_name;
}
/* for a single-file upload*/
$secured = securePath($newFile);
$secured_name = secureFileName($newFile_name);
copy($secured,"/apache/images/".$secured_name);
// for each multiple-file upload you must securePath & secureFileName it.
?>
You must add some checks to know if the $secured file exists, to avoid
"Warning" messages. If the file don't exists, you may want to log the user /
file info to a file.
Problem 2 - Writing Files on the System
---------------------------------------
This only works when you save the file with the original name (not if
you save it on a database or use another name for it);
The problem is based on the same idea:
If the $newFile_name is not checked, you can pass this as a filename
($newFile_name) someting like '../htdocs/index.html'
(Point your browser to:)
http://myhost.com/upload.php?newFile=/etc/passwd&newFile_name=../htdocs/index.html
Upload.php will copy '/etc/passwd' over '../htdocs/index.html'
(this is not good if your boss decides to check the homepage)
Fix:
<?
function secureFileName($newFile_name) {
$tmp = explode('/',$newFile_name); // split pieces of the path in '$tmp'
// usually, $tmp should have only one
// element
$tempName = $tmp[count($tmp)-1]; // get the last piece of the path
// (the name of the filename)
$secured_name = ereg_replace("\.{2,}","",$tempName);
// remove '..'+ from the tempname
return $secured_name;
}
$secured_name = secureFileName($newFile_name);
copy($secured,"/apache/images/".$secured_name);
?>
- Written by Martin Sarsale - martin@malditainternet.com - Buenos Aires 12/8/00
I'm waiting for comments :)
Anyone who wishes to make additions or changes to this
PHP Tip email them to webmaster@linuxguruz.org
This document is Copyright (c) 1999 by LinuxGuruz