Security
 


To identify or avoid security holes in applications written using PHP, follow these measures -

Avoid Using Variables When Accessing Files

The user could set the or variables and include files such as or remote files such as with malicious code. This malicious code could potentially delete files, corrupt databases, or change the values of variables used to track authentication status.

What to Look For

Search code for the following functions:

  • readfile
  • fopen
  • file
  • include
  • require

Possible Fixes or Improvements

  • Avoid using variables as file names. The $lib_dirvariable above could be replaced with a value defined by the PHP definefunction.

  • Check the file name against a list of valid file names. For example,

    $valid_pages = array(
    	"apage.php"   => "",
    	"another.php" => "",
    	"more.php"    => "");
    
    if (!isset($valid_pages[$page])) {
    	// Abort the script
    	// You should probably write a log message here too
    	die("Invalid request");
    }
  • If you must really use a variable from the browser, check the variable's value using code like the following:

    if (!(eregi("^[a-z_./]*$", $page) && !eregi("\\.\\.", $page))) {
    	// Abort the script
    	// You should probably write a log message here too
    	die("Invalid request");
    }
  • See Do Not Trust Global Variables for further steps on ensuring variables cannot be maliciously set.

  • Use the allow_url_fopenand open_basedirconfiguration variables to limit the locations where files can be opened from.

Escape characters in SQL statements

A common mistake is to use a variable value supplied by the user or the URL in an SQL query without escaping special characters. Consider the following fragment of code from a script designed to authenticate a username and password entered in a HTML form:

$query = "SELECT * FROM users WHERE username='" . $username . "' 
          AND password='" . $password . "'";

// the record exists function is defined elsewhere
if (record_exists($query)) {
	echo "Access granted";
} else {
	echo "Access denied";
}

his code would work when accessed using check.php?username=admin&password=x. However, if the code were accessed using check.php?username=admin&password=a%27+OR+1%3Di%271(and if magic_quotes_gpcwere disabled) then the password condition becomes Password='a' or 1='1'so that the admin user record would always be returned regardless of the password it contained.

This problem is partly avoided when the magic_quotes_gpcvariable is on in the php.inifile, meaning that PHP will escape quotes in GET, POST, and cookie data using the \character. However, magic_quotes_gpcis frequently disabled because it could make other code behave strangely. Given a line containing echo $usernamein the above code fragment, any occurrences of 'would be replaced by \'). Furthermore the magic_quotes_gpcvariable does not protect against variable values obtained from sources such as database records or files which a malicious user may have already modified during normal program operation.

What to Look For

Search for the query functions for your database. For example, if you are using MySQL, search for usage of the mysql_db_queryfunction.

Possible Fixes or Improvements

  • Use the built-in addslashesfunction or a similar function to escape quotes and backslashes in SQL statements with backslashes.

  • Enabling magic_quotes_gpcmay help, but don't rely upon it. (Enabling this setting and using addslasheswill produce errors).

  • If you are using variables which you expect to contain numbers in your SQL statement, ensure that they really do contain numbers. You can use various built-in PHP functions including sprintf, eregand is_long, to perform this check.

Do Not Trust Global Variables

If the register_globalsoption is enabled, PHP will create global variables for each GET, POST, and cookie variable included in the HTTP request. This means that a malicious user may be able to set variables unexpectedly. Consider the following code aimed to allow anonymous access to a single article and require authentication for all other articles:

// Assume $article_id is set by the URL
if ($article_id == 0) {
	$guest_ok = true;
}

if (!$guest_ok) {
	// Check user is authenticated using a function defined elsewhere
	check_auth();
} 

This code may appear to work, because the $guest_okvariable will generally be initialized to false. However, if a malicious user includes guest_ok=1in the URL, he will be granted access to any article in the system.

A similar problem can arise when you perform security checks when showing links to pages but do not perform security checks on the linked pages themselves. In a system where users are granted access to a select list of articles, you should perform security checks when producing the list of available articles and when displaying an article selected from the list. Without this checking, a malicious user could type URLs for articles to which he should not have access and view the article successfully. Another common variation of this problem is to implement a "Remember My Login" feature by storing a user identifier in a cookie, allowing users to change their cookie value to login as whomever they want.

What to Look For

This problem can appear almost anywhere in your code. Pay careful attention to the following areas:

  • Authentication and permission checking code
  • Use of variables before they are initialized. (You can set the error_reportingconfiguration variable to give a warning whenever uninitialized variables are used.)
  • Use of variables designed to be set by GET or POST requests.

Possible Fixes or Improvements

  • Disable register_globalsin your php.inifile. After making this change, you will need to use the $HTTP_GET_VARSand $HTTP_POST_VARSassociative arrays to access GET and POST inputs instead of using global variables. This can be tedious, but also far more secure.

  • If a "Remember My Login" function is required, include a password or a hard to guess random identifier in the cookie. (A "Remember My Login" function can still produce other holes such as malicious user who shares a machine with a legitimate user to gain access.)

  • Write code to initialize all global variables. The previous code fragment could be improved by initializing $guest_okto false at the start of the script.

  • Ensure session variables really do come from the session and not from a malicious user.

  • Write code to check that a global variable is not in the $HTTP_POSTor $HTTP_GETassociative arrays.

Avoid False Uploads

File uploads can suffer from a severe case of the untrusted global variables problem that is worth considering as an additional problem. When a file is uploaded, a PHP script is given a variable that provides the name of the temporary file where PHP saves the uploaded file. However, the user could construct a URL that sets this variable to a malicious value such as /etc/passwdand not upload a file. The responding script may then copy that file to an accessible location or display the file's contents to the user.

What to Look For

Examine all scripts that respond to file uploads. Searching for type="file"may help identify these scripts.

Possible Fixes or Improvements

  • Recent versions of PHP have the is_uploaded_fileand move_uploaded_filefunctions that allows the programmer to ensure that they are working with uploaded files.

  • If you are not sure that your code will be running on a recent version of PHP, set the upload_tmp_dirconfiguration setting and then perform input checking to ensure that the file you are working with is in this directory.

Escape HTML Characters in Text

What happens if somebody puts a tag in a posting to a discussion board? If you don't escape HTML characters in text either before you save or display it, all subsequent text on the page could be blinking. More severe versions of this attack are also possible; for example an attacker could write JavaScript that takes the browser to a competitor's site.

What to Look For

Identify pages which display text entered by untrusted users.

Possible Fixes or Improvements

  • Escape HTML appropriately either before you save it or before you display it. You can use PHP's built-in functions htmlspecialcharsor htmlentitiesfor this purpose.

  • If you want untrusted users to use HTML for formatting, you should perform validation to restrict the available HTML tags to a basic tags set, like and .