Set the PHP configuration option
``register_globals'' off, and use PHP 4.1.0 or greater.
PHP 4.1.0 adds several special arrays, particularly $_REQUEST,
which makes it far simpler to develop software in PHP
when ``register_globals'' is off.
Setting register_globals completely eliminates the most common
PHP attack, and prepares you for the time when this will be the default
for new installations.
If you're assuming that register_globals is off, you should check for
this first (and halt if it's not true) - that way, people who install
your program will quickly know there's a problem.
Note that few third-party PHP applications can
work with this setting, so it's hard to keep it off for an entire website
at this time.
Also, disabling ``register_globals'' is more difficult when you're being
hosted by a third party.
It's possible to set register_globals off for only some programs.
For example, for Apache, you could insert these lines into the file .htaccess
in the PHP directory (or use Directory directives to control it further):
php_flag register_globals Off
php_flag track_vars On |
However, the .htaccess file itself is ignored unless the Apache web server
is configured to permit overrides; often the Apache global configuration
is set so that AllowOverride is set to None.
So, for Apache users,
if you can convince your web hosting service to set ``AllowOverride Options''
in their configuration file (often /etc/http/conf/http.conf) for your
host, do that.
Then write helper functions to simplify loading the data you need
(and only that data).If you must develop software where register_globals might be on while
running (e.g., a widely-deployed PHP application),
always set values not provided by the user.
Don't depend on PHP
default values, and don't trust any variable you haven't explicitly set.
Note that you have to do this for every entry point
(e.g., every PHP program or HTML file using PHP).
The best approach is to begin each PHP program by setting all variables
you'll be using, even if you're simply resetting them to the
usual default values (like "" or 0).
This includes global variables referenced in included files,
even all libraries, transitively.
Unfortunately, this makes this recommendation hard to do, because few
developers truly know and understand all global variables that may be used
by all functions they call.
One lesser alternative is to search through HTTP_GET_VARS, HTTP_POST_VARS,
HTTP_COOKIE_VARS, and HTTP_POST_FILES to see if the user provided the data -
but programmers often forget to check all sources, and what happens if
PHP adds a new data source
(e.g., HTTP_POST_FILES wasn't in old versions of PHP).
Set the error reporting level to E_ALL, and resolve all errors reported
by it during testing.
Among other things, this will complain about un-initialized variables,
which are a key issues in PHP.
This is a good idea anyway whenever you start using PHP, because
this helps debug programs, too.
There are many ways to set the error reporting level, including in the
``php.ini'' file (global), the ``.htttpd.conf'' file (single-host),
the ``.htaccess'' file (multi-host), or at the top of the script
through the error_reporting function.
I recommend setting the error reporting level in both the php.ini file
and also at the top of the script; that way, you're protected if
(1) you forget to insert the command at the top of the script, or (2) move the
program to another machine and forget to change the php.ini file.
Thus, every PHP program should begin like this:
<?php error_reporting(E_ALL);?> |
It could be argued that this error reporting should be turned on
during development, but turned off when actually run on a real site
(since such error message could give useful information to an attacker).
The problem is that if they're disabled during ``actual use'' it's all
too easy to leave them disabled during development.
So for the moment, I suggest the simple approach of simply including it
in every entrance.Filter any user information used to create filenames carefully, in
particular to prevent remote file access.
PHP by default comes with ``remote files'' functionality -- that means
that file-opening commands like fopen(), that in other languages can
only open local files, can actually be used to invoke web or ftp
requests from another site.
Do not use old-style PHP file uploads; use the HTTP_POST_FILES array
and related functions.
PHP supports file uploads by uploading the file to some
temporary directory with a special filename.
PHP originally set a colleciton of variables to indicate where that filename
was, but since an attacker can control variable names and their values,
attackers could use that ability to cause great mischief.
Instead, always use HTTP_POST_FILES and related functions to access
uploaded files.
Note that even in this case, PHP's approach permits attackers to
temporarily upload files to you with arbitrary content, which is
risky by itself.
Only place protected entry points in the document tree; place all
other code (which should be most of it) outside the document tree.
PHP has a history of unfortunate advice on this topic.
Originally, PHP users were supposed to use the ``.inc'' (include)
extension for ``included'' files, but these included files often had
passwords and other information, and Apache would just give requesters
the contents of the ``.inc'' files when asked to do so when they
were in the document tree.
Then developers gave all files a ``.php'' extension - which meant that the
contents weren't seen, but now files never meant to be entry points
became entry points and were sometimes exploitable.
As mentioned earlier, the usual security advice is the best:
place only the proected entry points (files) in the document tree, and
place other code (e.g., libraries) outside the document tree.
There shouldn't be any ``.inc'' files in the document tree at all.
Avoid the session mechanism.
The ``session'' mechanism is handy for storing persistent data, but
its current implementation has many problems.
First, by default sessions store information in temporary files - so
if you're on a multi-hosted system, you open yourself up to many attacks and
revelations.
Even those who aren't currently multi-hosted may find themselves
multi-hosted later!
You can "tie" this information into a database instead of the filesystem,
but if others on a multi-hosted database can access that database with the
same permissions, the problem is the same.
There are also ambiguities if you're not careful
(``is this the session value or an attacker's value''?)
and this is another case where an attacker can force a file or
key to reside
on the server with content of their choosing - a dangerous situation -
and the attacker can even control to some extent the name of the file or key
where this data will be placed.
For all inputs, check that they match a pattern for acceptability
(as with any language), and then use type casting to coerce non-string data
into the type it should have.
Develop ``helper'' functions to easily check and import a selected list
of (expected) inputs.
PHP is loosely typed, and this can cause trouble.
For example, if an input datum has the value "000", it won't be equal to "0"
nor is it empty().
This is particularly important for associative arrays, because their
indexes are strings; this means that $data["000"]
is different than $data["0"].
For example, to make sure $bar has type double (after making sure it
only has the format legal for a double):
Be especially careful of risky functions.
This includes those that perform PHP code execution
(e.g., require(), include(), eval(), preg_replace()),
command execution
(e.g., exec(), passthru(), the backtick operator, system(), and popen()),
and open files
(e.g., fopen(), readfile(), and file()).
This is not an exhaustive list!
Use magic_quotes_gpc() where appropriate - this eliminates many kinds of
attacks.