Article | October 17th, 2008

If you’re including files based on user input you must think carefully about the security implications of this. When I say user input, I mean any value coming from outside your PHP script that is used in the formation of a file path. This could be as simple as a user clicking a link containing URL-parameter whose value is a predefined path to a file you wish to include.

Let’s first examine some code that accepts the name of a file and includes the contents of that file in a PHP page. The file named in the URL-parameters “body” is specified as an include.

<html>
<head>
<title>Blah</title>
<table border=1>
<tr>
<td width=100>
<a href=index.php?body=news.html>Test</a>

<td><? include("$body");?>
</table>

Be aware this is a security risk. Suppose a mischievous user enters a link into their browser with something like this:

index.php?page=../../../etc/passwd

Allowing directory navigation symbols into input exposes your host system’s password file and allow anyone on the web to read it. Moreover, it can expose any document on your web site. In this article, we will look at various solutions for allowing material to be included while at the same time closing this loophole.

You might think that you could just hardcode a directory or folder name into the path like this:

<a href=index.php?body=news.html>Test</a>
<td><? include('folder/'.$body);?>

But this does not work because directory navigation symbols (“../” and “./” can always be included in the path to get out of this directory. A malicious user can navigate anywhere, even out of the web tree.

Validating Include Paths


The solution is, instead of just blindly including the file, we check for any directory navigation symbols in the submitted value before the file is included. I will show you how to create a function we can run the value through before executing the include.

The following script demonstrates how it works. You will need a text file to experiment with:

kumquat.txt

<h3>Kumquat</h3>

<p>Any of several trees or shrubs of the genus Fortunella bearing small orange-colored edible fruits
with thick sweet-flavored skin and sour pulp.
</p>

And this PHP page: safeinc.php:

<?php

print "<h2>Testing Safe Include</h2>";

function is_safe_file_path($path) {
	if ((!eregi("^..",$path)) && (!eregi("^.",$path)))
	{
		return true;
	} else {
		return false;
	}
}

$include_path = "kumquat.txt";

?>
Including: <?php print $include_path; ?>
<div class="note" style="background: #eeeeee; color: #333333; border: 1px dashed;
   padding: 4px; font: 12px Arial">
<?php

if(is_safe_file_path($include_path))
{
	include($include_path);
}

?>

</div>

Put both files in the same directory and run safeinc.php to see the result. You can try entering “../” or “./” or “../../” or “../kumquat.txt” etc. into the $include_path to see it reject those paths with unsafe path symbols.

Be sure to form your full path, such as “fruits/kumquat.txt” before running it through the function and including.

Of course, you can have paths that go deeper in the directory tree like the previous, but not “../fruits/kumquat.txt” because that is what we are trying to prevent: navigating out of the folder we are in.

For this example, I also left out encapsulating the include into a function to make it easier to understand (and code!). Here’s a version of safeinc.php that wraps the include into a function.

safeincfn.php:

<?php

print "<h2>Testing Safe Include</h2>";

function is_safe_file_path($path) {
	if ((!eregi("^..",$path)) && (!eregi("^.",$path)))
	{
		return true;
	} else {
		return false;
	}
}

function safe_include($path) {
	if(is_safe_file_path($path))
	{
		include($path);
		return true; // indicate success
	}
}

$include_path = "kumquat.txt";

?>
Including: <?php print $include_path; ?>
<div class="note" style="background: #eeeeee; color: #333333; border: 1px dashed; padding: 4px; font: 12px Arial">
<?php

safe_include($include_path);

?>
</div>

Or a more compact function that folds all the features into one:

function safe_include($path) {
if ((!eregi("^..",$path)) && (!eregi("^.",$path)))
	{
		include($path);
		return true; // indicate success
	} else {
		return false;
	}
}

Or with built-in warning and script halt. Silent on success, display message on fail.

function safe_include($path)   {
if ((!eregi("^..",$path)) && (!eregi("^.",$path)))
	{
		include($path);
	} else {
		print "Warning - you do not have permission to access this area.";
		exit;
	}
}

There are no comments yet, add one below.

Leave a Comment


You must be logged in to post a comment.