Notice: Undefined index: p in
/home/keeline/public_html/iteachphp/index.php on line
109
Deprecated: Function eregi() is deprecated in
/home/keeline/public_html/iteachphp/index.php on line
124
Deprecated: Function eregi() is deprecated in
/home/keeline/public_html/iteachphp/index.php on line
124
Writing More Secure PHP Programs
Introduction
PHP is a remarkably powerful open-source server-side scripting language.
In very little code it is possible to do things which would be much more
complex to do in other similar languages like Perl, ASP, JSP, ColdFusion, or
C. However, the desire to hack together a quick solution can lead to insecure
web applications which can place your data and, in some cases, your server at
risk. This presentation will address some of these issues as they relate to
PHP and offer some suggestions to get you thinking in a direction which will
provide more security.
PHP is neither inherintly secure nor insecure. It is the responsibility
of the programmer of a web application, the database administrator and the
system administrator to ensure that security is not compromised at several
levels as described in Mark Nenadov's article
Developing Secure Web Applications.
- Operating System/Web Server Layer (Red Hat Linux with Apache)
- General Application Layer (issues common to any web application)
- Specific Application Layer (issues that relate directly to a given
application)
- Data Layer (issues related to the storage of data for your application)
Because there are many ways to run PHP, I will limit this discussion to PHP
4.x as a module to the Apache web server 1.3.x running on the Red Hat Linux 7.x
operating system. If you are running PHP as a CGI or Command Line application
or you choose to run on Windows, your mileage may vary.
Security Philosophy
There are several basic tenets which will help you protect your web apps
and your server.
Keep all software up to date.
In the past year there have been several updates to PHP to close security
holes related to file uploads and heap errors. Make sure you are running
4.2.2 or later. Additionally, your operating system, web server, SSL, and
database should be checked to ensure that all relevant bugfix/security
updates are applied.
Don't trust user input. Ever.
In previous versions of PHP, any form variable named would automagically
be created as a variable and the value from the input element would be
placed in the variable. Current versions of PHP have register_globals
in the off position. This solves some problems but creates others.
Initialize all variables and use Super Globals to retrieve values. Careful
use of extract()
can be helpful when getting large numbers of
variables from a form. Validate all data.
Restrict functions and which environmental variables may be changed.
Use disable_functions
to prohibit use of exec()
,
system()
, passthru()
, and backtick operators.
Consider blocking use of eval()
function unless absolutely
necessary.
Restrict access to filesystem.
Use safe_mode
or open_basedir
to limit which
files and directories may be accessed with PHP programs.
Protect your database connections.
If you are not using database abstraction, place the database connection
statements in a file which is outside of the web space and protected from
prying eyes.
Create database users who only have minimum specific rights to tables
required for a given application (ie select
-only user or
insert
-only user).
Handle includes and file uploads responsibly.
Make use of is_uploaded_file()
and move_uploaded_file()
functions when handling file uploads. Restrict size of upload along with
max_execution_time
and memory_limit
for scripts.
Validate MIME type of uploaded file before moving it to web-accessible
space. Consider using different, unpredictable, filenames for files in
web space or handler programs to prevent direct access.
Don't reveal that you are using PHP.
Security through obscurity can be weak and should not be used alone but it
can mislead people who would wish to misuse your web applications.
Keep all software up to date.
The minimum version of PHP which should be run is 4.2.2. Version 4.3.0pre2
was released on Oct 27, 2002. For updated information on this, see the
php.net site. Red Hat Linux
8.0 uses 4.2.2. You can locate RPM files at rpmfind.net. It is a good idea to check the
MD5 key signatures for all RPMs downloaded from a network to ensure that you
have not received a modified version from the trusted version (see man
rpm
for details).
When running a Red Hat Linux system, use the up2date
program
to be alerted to software updates related to security and bug fixes. For
details on how to install this program, see the tutorial on
yolinux.com for
Linux System Administration under up2date
. This will
help you track all of the distribution packages on your system, including
PHP, Apache, MySQL, OpenSSL, and others which might require upgrades to
keep up with security issues.
Don't trust user input. Ever.
The importance of this cannot be overemphasized. A variable within a PHP
script must be considered suspect unless it was explicitly set in the
program. All input from users should be validated to ensure that it
conforms to expected values. A simple PHP program might ask for a
password and then check it with the following code:
<?php
if ($password)
{ $valid_user = confirm_password($password); }
# ....
if ($valid_user == 1)
{ # Display data that an authorized user may see }
else
{ # Display login form }
?>
In versions of PHP where a value in /etc/php.ini
called
register_globals
is set to on
then it is
possible to circumvent the confirm_password()
function
by simply placing a get variable in the URL
(script.php?valid_user=1
). The results are obvious.
This could be fixed if the value of $valid_user
was set
to 0
prior to calling the confirm_password()
function.
We still have a problem, however, because we don't really know where
the value for $password
came from. Was it sent via POST
(the method used in the form) or was it sent as part of the GET request
in the URL by some miscreant? There are several "super global" arrays
in PHP which can be used to extract values from particular input
methods. Beginning in PHP 3, these had long names like
$HTTP_POST_VARS
. However, starting in PHP 4.1.0, shorter
names, like $_POST
were added to encourage their use.
Below is a table with the old and new names and the variables they
normally include.
Old Name | New Name | Description |
$HTTP_GET_VARS |
$_GET |
Values from HTML forms using GET method or from variables created
in URLs (script.php?var=value ). Limited in length
to the size of a URL which will be passed and processed. |
$HTTP_POST_VARS |
$_POST |
Values from HTML forms using POST method which are stored in body
of HTTP request rather than URL. These can be any length. |
$HTTP_POST_FILES |
$_FILES |
Variables set when a file is uploaded from an HTML form via
method described in RFC 1867 which uses method of POST
and enclding type of multipart/form-data . |
$HTTP_COOKIE_VARS |
$_COOKIE |
Variables retrieved from client which are stored as cookies in
the visitor's web browser. |
$HTTP_ENV_VARS |
$_ENV |
Variables retrieved from the server, also known as environmental
variables, with the PATH variable used to determine where
executable programs may be found, along with several others./td>
|
$HTTP_SERVER_VARS |
$_SERVER |
Variables retrieved from the server, often including environmental
variables, PHP variables, and information about the visitor which
were passed in the HTTP headers. |
$HTTP_SESSION_VARS |
$_SESSION |
Variables retrieved from the server (either from the /tmp directory
or a database) which are associated with a particular web visitor. |
-- |
$_REQUEST |
Variables from any input method (GET, POST, COOKIE). Do not rely
on this! |
-- |
$_GLOBALS |
Includes all of the variables in the super globals listed above. |
The longer variable names are considered depricated and may be removed
at some future date. However, if your server is using an older version
of PHP (shame on you), you can use the following line to create any of
the shorter array names from the long ones.
$_POST = $HTTP_POST_VARS;
Some security articles suggested values for variables_order
in the php.ini
file to set a specific sequence of which
variables are set or to ignore certain input methods, such as GET, for
automatic instantiation. Most of the articles closed that it would be
better to turn register_globals
off entirely for security.
Recent versions of PHP have register_globals
in the
php.ini
file set to off
which means that
variables will not automatically be created and populated with values
which cannot be trusted. However, even if you have installed a newer
version of PHP, say from RPMs, the php.ini
file with the
more secure settings will not be installed on top of your existing
php.ini
file. Instead, in the case of RPMs, it is likely
saved as /etc/php.ini.rpmsave
. Under these circumstances
it is a good idea to perform a comparison (diff /etc/php.ini
/etc/php.ini.rpmsave
) of the two files and try to understand
the reason for changes.
With register_globals
in the off
setting, a
new set of problems arises because many old programs and old tutorials
will no longer work. The first reaction was to add simple commands to
cause a script to act as if register_globals
was
on
. An example of this is placing
extract($_GLOBALS)
at the top of the script. This is
equivalent to the follwing code snippet:
foreach($_GLOBALS as $key=>$value)
{ ${$key}=$value; }
where each variable in the $_GLOBAL
associative array is
is created and populated. However, this quickly and effectively negates
the added security in favor of convenience. However, there are ways to
have improved security using extract()
which can limit
where the data can come from and in certain cases actually improve
security.
extract($_POST);
This first example accepts code only from the POST method and can stop
easy-to-implement exploits as part of the URL. However, it is possible
to create custom HTML forms or use the cURL
and other
libraries to send data via POST. In the long run, it won't stop a
determined intruder. A better way is to use additional features of the
extract()
function:
$fname=""; $lname=""; $email=""; $comment="";
extract($_POST, EXTR_IF_EXISTS);
This works on PHP 4.2.0 and later versions. It will only instantiate
variables from the POST array which already exist in the script. By
setting them equal to empty values, we can set not only their initial
value (an important security step as seen above) but also specify
which variables may be received from the POST array. Naturally, this
should be at the top of the program, above other variables which might
be "hijacked" by an intruder.
Be sure to evaluate the values of all user input with regular expressions
or other means to ensure that they hold values in the expected ranges.
If you plan to use user input as arguments for functions which execute
system commands, look into escapeshellarg()
and
escapeshellcmd()
to filter out efforts to run additional
system commands beyond what was intended in your script.
Restrict functions and which environmental variables may be changed.
As with your own internal and form variables, the variables from the
$_SERVER
array. Many of these can be rewritten simply by
making a variable assignment. This could have disasterous effects if
a value for the include_path
or (initially set in
php.ini
) or $LD_LIBRARY PATH
were altered to
cause a PHP program to include()
or require()
code from a remote site (not running PHP) rather than a local file from
a designated directory. This can be achieved with
ini_set("include_path","<value>");
or the
ini_alter()
function. Current versions of PHP protect
$LD_LIBRARY PATH
in the php.ini
with the
following line:
safe_mode_protected_env_vars = LD_LIBRARY_PATH
but only if safe_mode
is in the on
position
(more on that in the next section). There is a complimentary
parameter called safe_mode_allowed_env_vars
which is
discussed in Jordan Dimov's
On the Security of PHP, Part 2
It is also possible to block the use of certain functions which are
considered dangerous or that they reveal too much information about
the server. The php.ini
value called
disable_functions
in a comma-separated list. Below are
some functions which are candidates for inclusion:
Function | Description |
exec(), shell_exec(), system(), passthru(), popen(), proc_open() |
These functions execute command-line instructions which the web
server user has permissions to run. |
dl() |
Dynamically loads a PHP module. |
phpinfo() |
Displays a wealth of information about how PHP is configured, the PHP
modules available, and many values associated with the web server. This
includes version numbers of software which may be vulnerable. |
There are likely other examples of functions which you may wish to block.
This can be done either in the php.ini
or the Apache configuration
file (httpd.conf
) using the php_value
Apache
directive. Unfortunately, the disable_functions
value cannot be
changed in a VirtualHost
container. This would be helpful if you
trusted some domains more than others. For details of where a given value can
be changed, see
http://www.php.net/manual/en/function.ini-set.php.
Restrict access to filesystem.
Normally, a PHP script has access to the complete filesystem and all of the
resources on the server. Any file which the web server user ("apache" or
"nobody" on a Red Hat system) is available to PHP. This can allow a script
to display files which must be readable by all users on a system, such as
/etc/passwd
, as web page. If the server was not using shadow
passwords (where the password hashes are stored in /etc/shadow
-- only the root user may read the file).
One way to limit the risk of this is to enable safe_mode
in
php.ini
which restricts PHP scripts to files owned by the
web server user. There are several additional parameters which may be
used in conjunction with safe_mode
. Details on this may be
seen in
http://www.php.net/manual/en/printwn/features.safe-mode.php.
However, this may not be a complete solution. Another avenue to consider
is open_basedir
which limits the PHP script to files in and
below the directory specified. Like disable_functions
, this
can only be handled in php.ini
or httpd.conf
but not in a VirtualHost
container. According to Rasmus
Lerdorf's book, Programming PHP, you can use the Apache
directive php_admin_value
in a VirtualHost
container (rather than php_value
) to turn on
safe_mode
directives.
To restrict PHP scripts to the same user that owns the script, PHP is run
on some servers in CGI (Common Gateway Interface) mode under Apache's
suExec
restrictions. suExec
involves twenty
security checks which must all pass successfully before a script can run.
If it passes, a copy of the PHP interpreter is launched. This process
can cause a noticable delay in execution for the same reasons it does in
Perl scripts.
Protect your database connections.
Databases are of increasing importance to web sites. Many people create
a database user to access data for the script and use the following SQL
statement to assign permissions to a database:
GRANT ALL ON db_name.* TO db_user@localhost IDENTIFIED BY 'password';
This gives db_user
all types of rights to all of the
tables in the database db_name
. This is not wise. For
many pages with public access, the only permission required is
SELECT
or INSERT
. Only certain pages will
need DELETE
, DROP
, ALTER
, or
UPDATE
for example. Create separate users with the
minimum permissions needed for a given task.
Also consider the security benefits of placing the database server on
a separate machine with appropriate kernel-level firewall rules. This
may have some performance disadvantages but the advantages may be more
important.
Handle includes and file uploads responsibly.
PHP programers can create scripts which allow users to upload files.
It is very important to use the new is_uploaded_file()
and move_uploaded_file()
functions to make sure that
PHP is not fooled into working with internal files when it should be
limited to files uploaded via POST. Before moving a file, check the
MIME type to ensure that the file is the type expected. For example,
if you are creating an image gallery, you would not expect PHP scripts
to be uploaded.
Further, you may want to create a handler script to process image data
or use different filenames in the web space from those which are uploaded.
A database may be used to associate the names of the files.
Don't reveal that you are using PHP.
Security through obscurity is not very secure but it might prevent
you from being a constant target among the casual intruders.
You can reconfigure Apache to process files with extensions other
than .php, .php4, .php3, .phtml
. Some users use other
scripting extensions (.jsp, .asp, .cf, .pl, .cgi
) or
even .html
as the extension for PHP scripts. Also you
may wish to change expose_php
to off
.
Resources
Warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in
/home/keeline/public_html/iteachphp/index.php on line
130