PHP Security Issues and Solutions

russianmiserableSecurity

Jun 13, 2012 (5 years and 2 months ago)

316 views

1PHP Security Issues and Solutions
Thomas Böhne
May 17,2003AbstractMore and more people move from static HTML pages to dynamically gener-
ated websites.The most widely used language seems to be PHP,followed by ASP
(and VB-Script) and Perl.
While the dynamic approach offers many advantages,most users seemto over-
look the risks that arise fromdynamic websites.We show some basic attacks,and
some principles to a more secure design.
1 Introduction
As for all Internet applications,security is a basic necessity.As for many Internet
applications,security is often not enforced properly.
In the following sections we show some common errors solutions to the problems.
Not all of themwill work in all environments;most of themrely on register_globals
being enabled,and safe_mode being disabled.
2 Check variables fromuser space
One of the basic advantages in dynamic websites (CGI,to be more precise) is that
not only URLs are requested,but that variable assignments can be send as well.The
variable assignments are often encoded in URLs delivered by a previous script run,but
can be modified easily before resubmitting.Thus,the content of no variable that was
sent froma user can be trusted.
Especially if the variables should be used as options to function calls like popen
or unlink,it is important to check the content very clearly.Consider the following
function calls:$fp = popen ("/bin/ls $homedir/public_html/pictures/$_GET[’pictures’]","r");
echo"Expression $_GET[’expression’] evaluates to:";
echo eval("echo $_GET[’expression’]");
mysql_query("SELECT * FROM names ORDER BY $_GET[’order’];",$link);
Calling this script with the options pictures=foo;rm *,expression=unlink("~/.bashrc")
or order=foo;DROP TABLE names would lead to much data being lost (Actu-
ally,it would not,since the special characters must to be encoded properly.But most
browsers can do that automatically).
Many similar examples could be given here;the only solution to the problemis that
calls to sensitive functions should either not include user input at all,or only if it has
been checked properly (see below).It is sometimes difficult to distinguish sensitive
2functions fromnot so sensitive functions — in that case,one should always assume the
worst and check the input.
The following examples showsome ways to make sure that no malicious statements
are executed:•To check that the pictures variable contains only (more or less valid file-
names),one could use a construct like this:$pic = $_GET[’pictures’];
if (!preg_match(’/ˆ[A-Za-z0-9]+$/’,$pic))
$pic ="default";
$fp = popen ("/bin/ls $homedir/public_html/pictures/$pic","r");
After this test,the variable $pic contains only characters and digits.This should
be enough to specify a directory,and prevents the execution of other commands.•A similar construct can be used for the second example:if (!preg_match(’/ˆ[0-9+-*/]+$/’,$expression))
Now $expression should only contain spaces,mathematical operators and
digits.•For variables that should only take a distinct number of values,we can just check
if it is a valid choice:$order = $_GET[’order’];
if (!in_array($order,array("names","addresses","telephone")))
$order ="names";
mysql_query("SELECT * FROM names ORDER BY $order;",$link);
The array contains all valid choices for $order,and if the user submits a value
that is not in the array,it defaults back to"names".•Often,number must be checked to be in a certain range.The following code
makes sure that menu is an integer between zero and seven:$menu = abs((int) $_GET[’menu’]) % 8;
3 Use constants where possible
A constant type should always be used for values that do not change;this paradigm
is taught in many programming courses and part of most tutorials.Many current pro-
gramming languages provide for constants,but still many programmers ignore them
for whatever reasons.Unfortunately,the declaration of constants in PHP using a func-
tion call is not intuitive and requires more typing (keystrokes,not type conversions)
than a variable assignment.The following example shows a relatively save (although
not perfect) construct:
3#extract from index.php
if ($username =="admin"&& $password =="secret")
define("AUTHENTICATED",true);
else
define("AUTHENTICATED",false);
include("restricted.php");
#extract from restricted.php
if (AUTHENTICATED)
doSecretStuff();
Since variables and constants reside in different name-spaces,calling restricted.php?AUTHENTICATED=true
will not gain access to the secret part (i.e.,$AUTHENTICATED is different fromAUTHENTICATED).
4 Check file access
In many cases,users split up larger projects into small PHP files.Consider the follow-
ing example,where an input variable is properly checked in index.php but used in
showfile.php:#extract from index.php:
if (!preg_match(’/ˆ[A-Za-z0-9]+$/’,$filename))
$filename ="default";
include("showfile.php");
#extract from showfile.php
include("$filename");
Readers familiar with PHP will recognize quickly that a call to showfile.php?filename=../../../etc/passwd
would bypass the checking of $filename in index.php (the code in index.php
makes sure that $filename consists of only letters and digits,to disable ascending in
the directory tree).
One possible solution to the problem would be checking variables exactly where
they are needed;however,grouping security checks in a central place seems to be
reasonable.In some (but not all) situations the variable can be replaced by a constant
(this would eliminate the problem,see previous section).It is also possible to only put
the official starting files (e.g.,index.php) in a publicly accessible directory on the
webserver,and hide the included files somewhere else.Unfortunately,this is not always
possible since some web-hosting services do not offer access to private directories.A
third straightforward solution — that should work on any PHP account — is to define
a constant in the starting pages,and require the constant to be set at the beginning of
all included files:#extract from index.php:
define("VALID_ENTRY",true);
if (!preg_match(’/ˆ[A-Za-z0-9]+$/’,$filename))
$filename ="default";
include("showfile.php");
#extract from showfile.php
if (!defined("VALID_ENTRY"))
die("This script cannot be accessed directly.");
include("$filename");
4Note that it is crucial to use a constant instead of a variable.The test for $valid_entry == true
could easily be bypassed by showfile.php?filename=../../../etc/passwd&valid_entry=true.
5 Use safety mechanisms provided by the programming
language
Earlier versions of PHP praised the feature that CGI variables (such as GET and POST)
were implicitly made global and had the same syntax as normal variables.In fact,the
feature called register_globals was very convenient in many places.But soon
people realized,that it is also a perfect tool to shoot oneself in the foot.Basically,
all variables in scripts can be initialized by any user...Finally,register_globals
were disabled by default (somewhen around version 4.0),resulting in a flood of post-
ings by PHP users whose scripts did not run properly anymore.However,disabling
register_globals was definitely the correct decision.Still,many installations
have it enabled,and it is the root of many evils.So the first thing that every PHP user
should do,is turn it off (it can be done everywhere;in the PHP configuration file,the
Apache configuration file,a.htaccess file,or in the code using ini_set).
Consider the following simple example:if ( $username =="admin"&& $password =="secret")
$auth = true
if ($auth == true)
doSecretStuff();
Many people would approve this implementation to be relatively secure.Although
many elements could be improved,this code would work in many programming lan-
guages other than PHP.But if register_globals is enabled,requesting file.php?auth=true
would return the secret content.Disabling register_globals prevents this kind of
attack.
Another security option that PHP provides is safe_mode.It disables many risky
function calls (that are usually not needed) or restricts them in useful ways.However,
safe_mode can only be enabled in the Apache or PHP configuration file.It seems to
be disabled on most web-hosting services.
Note:It is clear that it should be difficult to disable
safe_mode.But wouldn’t it be nice if it could be
enabled anywhere?Send me comments!
By default,PHP performs basic error handling.However,it seems to be a much
better idea to enable all warnings and notices (error_reporting(E_ALL)) on the
development system and turn them off (error_reporting(0)) on the actual web
system(or even better:performown error handling).
TODO:Write about require(once),assert,die/exit/continue,and
preventing the webserver fromsending php source code.
6 Expect the worst - deny by default
A basic security concept is deny everything,allow exceptions,meaning that safer op-
tions must always be preferred.I.e.,every authentication script should have the form:
5$authenticated = false;
if ( your_favorite_access_control_code(options) ) {
$authenticated = true;
}
if ($authenticated) {
doSecretStuff();
}
In the example above,the variable $authenticated indicates that the user is
authenticated after all options are successfully checked.Look at the following bad
example that violates this rule:switch ($username) {
case"admin":if ($password!="secret")
$loginfailed = true;
break;
case"fred":if ($password!="barney")
[...]
default:$loginfailed = true;
}
if ($loginfailed)
showLoginForm();
else
doSecretStuff();
The unclear structure and the default:$loginfailed = true statement
make it less obvious that this snippet of script does not work at all.A valid user-
name combined with any password will provide access to the secret part.The problem
is that the variable $loginfailed1.is not initialized,2.cannot be properly initialized,and3.the default value is used to grant access (in PHP,uninitialized variables are
treated as 0,"",false or NULL).
7 Use well-known security implementations
Programmers should not try to reinvent the wheel and come up with a (more or much
less) bright solution for problems that have already been solved.Basic authentication
features are already implemented in PHP (PHP_AUTH,only available if PHP runs as an
Apache module);restricted directory access can be implemented using.htaccess
methods on Apache servers.Unfortunately,both transfer the passwords in plain text.
To prevent plain text passwords from being sent over the network,encryption and
hash functions can be used.E.g.,implementations of the MD5 algorithm are freely
available on the Internet as JavaScript functions (MD5 is part of the PHP library as
well).
68 Status
This document is still under development.If you have any comments,suggestions,or
if you found errors,please do not hesitate to send me email.
©Thomas Böhne,May 2003
9 About this document
This document was automatically generated froma XML source using xsltproc and
tbook (see http://sourceforge.net/projects/tbookdtd).