PHP Security, Part 3
by John Coggeshall
10/09/2003
Welcome to another installment of PHP Foundations. Last time, I discussed
the
potential security breaches that can occur when using system calls from PHP
scripts (and some methods of protecting yourself from them). As the last
part of my series focusing on the pitfalls and techniques involved when writing
secure PHP applications, this article will not introduce any new potential
security breaches. Rather, today I will finish my discussion of security by
introducing the tools PHP provides to handle error logging and reporting and
summarize the main points that I have covered in this series.
Logging and Security
In order for malicious users to take advantage of your programs, they must
first know of your program's weaknesses. To do so, a malicious user
needs to probe your web applications to gather as much information as possible
about them. A very effective and common approach is to attempt to cause errors.
For example, let's assume a malicious user has entered invalid data into a
login form, producing the following standard PHP error message:
Notice: Undefined index: content in /usr/local/apache/htdocs/index.php
on line 22
What can be determined from this message? Obviously, the error indicates
that there is an array with a key of content that is undefined in
the file index.php, on line 22. From this one error message, a
malicious user now has a lead on a potential weak point from which to pollute
your application's data. Maybe that undefined index is something in
$_GET or $_POST. In fact, you could even get a
general idea of the code flow of your scripts by paying attention to what line
number an error occurs on, under different circumstances. Furthermore, the
nature of the error can also provide more detailed information, including where
the script stores files on the server (if working with filesystem commands),
the format of the queries used by the script (if working with a database), and
much more. Although it is rarely desirable for your users to see detailed error
messages when something goes wrong, it can even become a security hazard under
certain circumstances.
The first step to solving this problem is to take the necessary steps to
minimize the potential for runtime errors within your scripts. Any good PHP
application should ensure that all variables are defined, or at least checked
with isset(), if dealing with superglobal data. However, no
matter how much thought in put into your application, it is unrealistic to
expect that you've accounted for every possible circumstance. For this reason,
security-sensitive web applications also implement error-handling and -logging
systems.
The PHP Error-Logging Mechanism
The point of error handling and logging, at least when it comes to
security, is to deny a malicious user information about your system, while
still providing the developer access to that information. An appropriate error-logging system will record attempts to compromise the security of your
application, giving you the information needed to strengthen your application's
security where and as necessary.
Implementing a logging system in PHP can be as simple or as complex as you
like. PHP internally offers several options to the developer as to how errors
are dealt with and logged. For instance, although PHP, by default, will display
errors that occur during the processing of a script to the browser, it also
can be configured to log those errors without displaying them. This behavior is
controlled by the log_errors and display_errors
configuration directives in the php.ini file. Turn error display on and off
as your development needs change. A common practice is to display errors
without logging them during the development and debugging of an application.
The finished product will do the opposite: logging, but not displaying,
errors.
Although you are familiar with how PHP displays error messages to the
browser, where does PHP log errors when logging is enabled? Another
configuration directive, error_log, controls the behavior of PHP's
error-logging mechanism. This directive can be set to a filename, the string
syslog, or completely omitted (the default). When the
error_log directive is completely omitted from
php.ini, PHP will use the logging facilities provided by the web
server (such as the Apache error log) for its logging purposes. If set to a
filename, PHP will write all messages to that file, as long as system
permissions allow it. If set to the keyword syslog, PHP will log
messages via the operating system's logging facilities. On UNIX-based systems, this is the
standard OS syslog, and on Windows
NT and XP systems, this is the event log on.
Although PHP will automatically take care of logging error messages for you
when you are using the internal error handler, when using a custom error
handler (discussed later in this article), you must log these errors yourself.
To do this, PHP provides the error_log() function, with the
following syntax:
error_log($message [, $message_type [, $dest [, $extra_info]]]);
Depending on the value of the optional $message_type parameter
(the default is zero), one of the following things will occur:
- If
$message_type is zero, the error message
$message will be recorded in the logging facility specified by the
error_log configuration directive.
- If
$message-type is 1, the error message $message
will be emailed to the address specified by the $dest parameter.
Any desired additional mail headers can be specified in the
$extra_info parameter.
- If
$message_type is 3, the error message
$message will be written to the file specified by the parameter
$dest.
Note: You might have noticed that there is no behavior if
$message_type is 2. This is a relic from PHP version 3, where
remote debugging was provided as part of the standard release. It is no longer
available in PHP4.
The error_log() function can be used anywhere for logging, but
is used most often as part of a custom error handler. See the PHP manual for
examples.
The PHP Error Model
Understanding PHP's error model is almost as important as having an
appropriate error-logging mechanism. This model governs what kinds of errors
PHP logs, and when and how it logs them. To understand the error model, you must
be familiar with the types and meanings of errors that can occur in PHP. This
information can be found in the PHP manual as well as the following list:
E_ERROR signifies that a serious problem has occurred
internally within PHP or a PHP extension (for instance, failure to allocate
memory).
E_WARNING usually occurs to bring attention to problems that
may exist with your code that will cause it to not work properly, such as
passing a scalar value to an internal function that expects a complex value
such as an array.
E_NOTICE is the least significant of the internal errors caused
by PHP. This error almost never signifies that the application is performing
improperly. Instead, this error message is generally reserved to warn the
developer of things such as variables used before initialization.
E_CORE_ERROR is identical to E_ERROR in severity, but is
only generated during the initial initialization of the engine.
E_CORE_WARNING is the non-fatal counterpart of
E_CORE_ERROR and is similar to E_WARNING. It is only
generated during initial initialization of the engine.
E_COMPILE_ERROR signifies a serious error during compilation by
the Zend Scripting Engine.
E_COMPILE_WARNING is a non-fatal warning, like
E_WARNING, generated by the Zend Scripting Engine during
compilation.
E_USER_ERROR is reserved strictly for use with the
trigger_error() function (discussed later). By default, PHP treats
this error the same as E_ERROR, displaying the error and halting
execution.
E_USER_WARNING is reserved strictly for use with the
trigger_error() function. By default, PHP treats this error the
same as E_WARNING.
E_USER_NOTICE is reserved strictly for use with the
trigger_error() function. Unlike E_NOTICE errors, which are ignored by default, PHP will display E_USER_NOTICE
errors to the user.
Setting the Error Reporting Level
The error_reporting configuration directive determines which
error messages are actually logged or displayed to the browser when they occur.
This directive is a "bit field," meaning that error messages can be combined
together in any way desired using the AND, OR, and
NOT Boolean logic operators. In the php.ini file, the
ampersand (&) symbol indicates AND, the pipe
(|) symbol indicates OR, and the tilde symbol
(~) indicates NOT. Therefore, to
display or log only serious errors (no warnings or notices), you could use:
error_reporting = E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR
Along with the standard error types, the error_reporting directive also
allows a special error type, E_ALL, which represents all possible
errors, equivalent to ORing each of the error types together.
Likewise, E_ALL includes all of the error types. The code below
will display or log all errors except those in the E_USER
family:
error_reporting = E_ALL & ~E_USER_ERROR & ~E_USER_WARNING & ~E_USER_NOTICE
By default, the error_reporting configuration directive will log or display
all errors except E_NOTICE errors.
Regardless of how PHP is configured to handle errors, there are several ways
to change what actually happens at runtime. The easiest method is to use the
special error-silencing operator @. Place this operator in front
of a statement, and PHP will silently ignore any error that occurs during the
evaluation of the expression. For instance:
<?php
echo @$myvar;
?>
will no longer cause an E_NOTICE error, even though
$myvar is undefined. Instead, PHP will silently ignore the error
and print nothing.
Another method of altering PHP's configured error handling is by using the
error_reporting() function. This function directly modifies the
internal value of the error_reporting configuration directive. It
has the following syntax:
error_reporting([$error_value])
where the optional $error_value parameter is the new error
value. PHP defines constants representing each of the types of errors shown
above, which can be combined using PHP's logic operators. The following
statement instructs PHP to respond to only serious errors, as per an earlier
example:
<?php
error_reporting(E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR);
?>
Regardless of the parameter provided to the error_reporting()
function, the return value will always be an integer representing the
previously set error_reporting directive value. This allows you to
set a custom error reporting level for a small segment of your web application
and restore it back to its previous value easily:
<?php
$old_error = error_reporting(0); /* Turn off all error reporting */
/* do things here
error_reporting($old_error); /* restore previous error reporting status. */
?>
Custom Error Handlers
Beyond all of PHP's standard error-handling facilities, the language
also allows you to define a custom error handler. A custom error handler allows
you to strictly control how your web application will respond to an error
generated either by PHP internally or triggered by the
trigger_error() function. To use custom error handling, define a
function of the following form:
<?php
function my_handler($error_code, $error_msg [, $error_file [,
$error_line [, $vars]]]) {
/* Error handling routines here */
}
?>
As described in the above snippet, any custom error handler must accept
$error_code and $error_msg parameters corresponding
to the error code (such as E_ERROR) and the error message
associated with the error. Your error handler can also optionally accept up to
three additional parameters: $error_file, $error_line,
and $vars. The first two parameters represent the PHP file and
line number where the error occurred. The last parameter, $vars,
is an associative array containing the name and value of each variable that was
available at the time the error occurred.
Once you have created a function of the correct form, it must be registered
with PHP as the active error handler. Use the set_error_handler()
function, which has the following syntax:
set_error_handler($func_name)
where $func_name is a string containing the name of the
function you defined (using my example, my_handler). When this
function executes, it returns the value of the old error handler, which can be
saved to restore the previous error handler as desired. When using a custom
error handler, there are several things to consider:
- The custom error handler cannot process "fatal" errors. The error handler
will only be called for
E_WARNING, E_NOTICE, and the
E_USER family of errors. All other errors will be handled by the
internal error handler as if there were no custom error handler.
- When using a custom error handler, PHP will call it when any of the above
errors occur, regardless of the setting of the
error_reporting
configuration directive. It is up to the error handler to use
error_reporting() and act accordingly.
- Unless the custom error handler terminates the execution of the script
using
die() or exit() statements, PHP will continue
executing the script, regardless of the error. It is up to the error handler to
stop execution if necessary.
Today's final function is trigger_error(). This function is
used to trigger user-defined errors. It has the following syntax:
trigger_error($msg [, $error_code])
where $msg is a string containing an error message describing
the error, and the optional parameter $error_code is an error from
the E_USER family. PHP will automatically use
E_USER_NOTICE when no error code is provided. This function is
designed to be used in conjunction with a custom error handler, though if no
custom error handler exists, PHP will respond to the error using the internal
error handler.
A Final Word on Security
Before I finish this series on security in PHP, I want to wrap up this
discussion and recap the major themes that I have discussed over the past
several columns. When writing a web application in PHP (or any application in
any language), the single biggest thing that you can do to improve the security
of your application is to keep potential security implications in mind. Are you
using system calls? What are you doing to protect them from being taken
advantage of? How will your application respond to invalid user input? What
precautions are you taking to filter user input? You should ask yourself
all of these questions as you develop.
In the end, any text (including this one) can only teach you so much. Once
you have learned the basic concepts, such as logging and data validation, it is
up to you to apply them to your application. Diligence and careful attention to
detail are the best tools any developer has to ensure the security of his
applications. Although malicious users use standard tactics to cause your
programs to behave in an unintended way, by the very nature of maliciousness,
they will always attempt to do things that you may not have considered.
In my next article, I will switch gears to step back from security. I'll
discuss tools to assist you in manipulating and working with data. Until then,
happy scripting!
John Coggeshall
is a a PHP consultant and author who started losing sleep over PHP around five years ago.
Read more PHP Foundations columns.
Return to the PHP DevCenter.