PHP Security, Part 3
by John Coggeshall10/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
|
Related Reading
Secure Coding: Principles and Practices |
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_typeis zero, the error message$messagewill be recorded in the logging facility specified by theerror_logconfiguration directive. - If
$message-typeis 1, the error message$messagewill be emailed to the address specified by the$destparameter. Any desired additional mail headers can be specified in the$extra_infoparameter. - If
$message_typeis 3, the error message$messagewill 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_ERRORsignifies that a serious problem has occurred internally within PHP or a PHP extension (for instance, failure to allocate memory).E_WARNINGusually 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_NOTICEis 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_ERRORis identical toE_ERRORin severity, but is only generated during the initial initialization of the engine.E_CORE_WARNINGis the non-fatal counterpart ofE_CORE_ERRORand is similar toE_WARNING. It is only generated during initial initialization of the engine.E_COMPILE_ERRORsignifies a serious error during compilation by the Zend Scripting Engine.E_COMPILE_WARNINGis a non-fatal warning, likeE_WARNING, generated by the Zend Scripting Engine during compilation.E_USER_ERRORis reserved strictly for use with thetrigger_error()function (discussed later). By default, PHP treats this error the same asE_ERROR, displaying the error and halting execution.E_USER_WARNINGis reserved strictly for use with thetrigger_error()function. By default, PHP treats this error the same asE_WARNING.E_USER_NOTICEis reserved strictly for use with thetrigger_error()function. UnlikeE_NOTICEerrors, which are ignored by default, PHP will displayE_USER_NOTICEerrors 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 theE_USERfamily 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_reportingconfiguration directive. It is up to the error handler to useerror_reporting()and act accordingly. - Unless the custom error handler terminates the execution of the script
using
die()orexit()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.