This is the second part of the translation. Start reading better with the first .So, after applying the methods described in the first part, can we stop worrying? Unfortunately not. Which file extensions will be transferred to the PHP translator will depend on the server configuration. The developer often does not know and does not control the configuration of the web server. We saw web servers with a configuration such that .html and .js files were executed as php. Some web applications may require that .gif or .jpeg files be interpreted by PHP (this often happens when images, such as graphs and charts, are dynamically built on the server by PHP itself).
Even if we know exactly which file extensions are interpreted by PHP, we have no guarantee that this will not change in the future when other applications are installed on the server. By that time, you can forget that the security of our server depends on these changes.
If you have a web server based on Microsoft IIS, then you need to keep a few more points in mind. Unlike Apache, the Microsoft IIS server supports the execution of “PUT” requests that allow users to upload files directly, bypassing PHP. PUT requests can be used to upload files to the server if system permissions allow it to be done by IIS (it is running as IUSR_MACHINENAME). This can be configured using the Services Manager:
')

To allow PHP to upload files, you must change the file system permissions to make the directory writable. It is very important to make sure that the permissions of IIS do not allow writing files. Otherwise, users will be able to upload arbitrary files using PUT requests, bypassing any checks you make in PHP.
Indirect access to downloaded files
Sometimes it is impossible to give direct access to the download directory. This may be due to the fact that this directory is not under the root of the site or its access is restricted using server settings or .htaccess.
Consider the following example (upload5.php):
<?php
$uploaddir = '/var/spool/uploads/' ; # Outside of web root
$uploadfile = $uploaddir . basename($_FILES[ 'userfile' ][ 'name' ]);
if (move_uploaded_file($_FILES[ 'userfile' ][ 'tmp_name' ], $uploadfile)) {
echo "File is valid, and was successfully uploaded.\n" ;
} else {
echo "File uploading failed.\n" ;
}
?>
* This source code was highlighted with Source Code Highlighter .
Users cannot simply refer to / uploads / to upload files, and we must provide an additional opportunity for this (view5.php):
<?php
$uploaddir = '/var/spool/uploads/' ;
$name = $_GET[ 'name' ];
readfile($uploaddir.$name);
?>
* This source code was highlighted with Source Code Highlighter .
The view5.php code has a serious vulnerability. An attacker can use this code to read any file that can be read at the rights level of the web server. For example, if you call
www.example.com/view5.php?name=../../../etc/passwd , you may be able to read the password file.
This bug can be fixed using the result function dirname (realpath ()), which returns the actual path to the file. Thus, if this path is incorrect, then we do not show the file. Similarly, with basename (), we get only the file name, which is already attached to the correct download directory. - Approx. translator.Uses of local include (Local file inclusion attacks)
This is one of the most terrible security holes on sites. It is so well known that in reality it almost does not manifest itself. But, as they say - “repetition is the mother of learning”. - Approx. translator.The last example stores the downloaded files outside the root, where they cannot be accessed directly and executed. Although it is safe, an attacker may have a chance to take advantage of this if another vulnerability is present in the code - use include. Suppose that we have some other page that contains the following code (local_include.php):
<?php
// ... some code here
if (isset($_COOKIE[ 'lang' ])) {
$lang = $_COOKIE[ 'lang' ];
} elseif (isset($_GET[ 'lang' ])) {
$lang = $_GET[ 'lang' ];
} else {
$lang = 'english' ;
}
include( "language/$lang.php" );
// ... some more code here
?>
* This source code was highlighted with Source Code Highlighter .
This is a common piece of code that usually occurs in multilingual web applications. Such code can provide different include files depending on user preferences.
The code has an include-vulnerability. The attacker may force this page to include any file from the file system, for example:

This request causes local_include.php to include and execute “language /../../../../../../../../ tmp / phpinfo”, which is simply / tmp / phpinfo. The attacker can only execute files that are already on the server side, thus its capabilities are limited.
But, if an attacker is able to upload files, even outside the root of the site, and he knows the name and location of the downloaded file, using a similar vulnerability, he can execute arbitrary code on the server.
Previously, there was a php setting that allowed you to include files by url, so you could execute code that might not be on the server. In recent versions of php, there is no such possibility in principle for security reasons. - Approx. translator
Eventually
When providing protection, it is important to prevent the attacker from knowing the name of the downloaded file. This can be done by randomly generating the names of the downloaded files, while preserving the original ones in the database.
Consider an example (upload6.php):
<?php
require_once 'DB.php' ; // We are using PEAR::DB module
$uploaddir = '/var/spool/uploads/' ; // Outside of web root
$uploadfile = tempnam($uploaddir, "upload_" );
if (move_uploaded_file($_FILES[ 'userfile' ][ 'tmp_name' ], $uploadfile)) {
// Saving information about this file in the DB
$db =& DB::connect( "mysql://username:password@localhost/database" );
if (PEAR::isError($db)) {
unlink($uploadfile);
die "Error connecting to the database" ;
}
$res = $db->query( "INSERT INTO uploads SET name=?, original_name=?, mime_type=?" ,
array(basename($uploadfile,
basename($_FILES[ 'userfile' ][ 'name' ]),
$_FILES[ 'userfile' ][ 'type' ]));
if (PEAR::isError($res)) {
unlink($uploadfile);
die "Error saving data to the database. The file was not uploaded" ;
}
$id = $db->getOne( 'SELECT LAST_INSERT_ID() FROM uploads' ); // MySQL specific
echo "File is valid, and was successfully uploaded. You can view it <a
href=\"view6.php?id=$id\">here</a>\n" ;
} else {
echo "File uploading failed.\n" ;
}
?>
* This source code was highlighted with Source Code Highlighter .
View the downloaded file (view6.php):
<?php
require_once 'DB.php' ;
$uploaddir = '/var/spool/uploads/' ;
$id = $_GET[ 'id' ];
if (!is_numeric($id)) {
die( "File id must be numeric" );
}
$db =& DB::connect( "mysql://root@localhost/db" );
if (PEAR::isError($db)) {
die( "Error connecting to the database" );
}
$file = $db->getRow( 'SELECT name, mime_type FROM uploads WHERE id=?' ,
array($id), DB_FETCHMODE_ASSOC);
if (PEAR::isError($file)) {
die( "Error fetching data from the database" );
}
if (is_null($file) || count($file)==0) {
die( "File not found" );
}
header( "Content-Type: " . $file[ 'mime_type' ]);
readfile($uploaddir.$file[ 'name' ]);
?>
* This source code was highlighted with Source Code Highlighter .
Now downloaded files cannot be directly executed (because they are stored outside the root). They cannot be used in include-vulnerabilities, because the attacker does not have the ability to find out the name of the downloaded file in the file system on the server. There is some problem with the "intersection" of files, because the files are tied to a numeric index, and not to its name. I would also like to indicate the use of PEAR :: DB for SQL queries. In our SQL, question marks are used as spaces for query variables. When data received from a user is transferred to a query, their types are automatically placed, preventing problems with SQL injections.
An alternative to storing files in the file system is to store files directly in the database using BLOB fields. This approach also has its advantages, but in most cases it does not apply, because of its huge resource intensity.
Other problems
There are many more things that you should pay attention to when developing systems with uploading files to the server, namely:
- Denial of service. The user can upload several large files, thereby occupying all the free space on the server. This is solved by setting a limit on the size of the uploaded file and on the number of uploaded files from one user per day.
- Performance. In the last example, with frequent requests to display a file, the view may be a bottleneck. If the server is heavily loaded, then it is recommended to use another one, intended only for storing static content on which the php code cannot be executed. Another way to improve performance is to use a caching proxy server, which prevents repeated processing of static content on the server side by issuing appropriate headers.
- Access control. In all the examples above, we assumed that any user could view any downloaded file. However, it may be necessary that only the user who downloaded the file could view it. In this case, the file owner information should be saved during the download. When viewing the file should be an appropriate check.
Conclusion
When developing uploading files to a server, it is necessary to take into account the vulnerabilities described in the article in order not to become a victim of malicious users.
It is best not to allow users to directly access downloadable files. This can be done by storing the downloaded files outside the root of the site or by denying access to this directory using the web server configuration.
Another important security measure is not to store files on the server under the original file names. This will prevent the possibility of include-vulnerabilities, even if they exist, as well as make any manipulation of the file names for the attacker impossible.
Checking the image format via PHP does not give any guarantee that this file cannot be executed as a php script. You can create a valid image, which at the same time will be an executable php-script.
Data from the client, such as Content-Type and file extension, cannot be trusted at all. They are very easy to fake. Moreover, the list of executable extensions depends entirely on the web server, and there is no guarantee that it will not change over time.
Performance is very important, but it’s absolutely impossible to ensure safe uploading of files to the server without harming it.