Trends, Techniques, Tips & Tricks for the PHP Scripting Language

PHP Security Basics: Authentication

| February 28, 2009
In order to customize a user's application experience, it is common to create a user account and require that the user login or authenticate in order to gain access to the account. Such customization is undoubtedly useful, perhaps even central to application functionality. However, it immediately creates privacy issues and, where elevated access privileges are granted to certain users, it also creates security issues. Where elevated privileges enable access to valuable resources, attackers will attempt impersonation.

So, how can we prevent impersonation? There are two main attack strategies: credential capture and post-authentication session hijacking. We'll look at session hijacking next post. Credential capture can take a number of forms. Simple guessing can usually be defeated by enforcing a delay between login attempts, and the recent practice of adding a captcha after every nth failed attempt is a wise practice to adopt. SSL encryption is the best defense against password sniffing. At one time, there wouldn't have been a whole lot more to say as part of a "basics" discussion, however today, persistent login has become a common user expectation.

A persistent login is simply the persistence of authentication across multiple sessions. That is, requiring reauthentication only for privilege escalation (as well as financial transactions, etc.) or after some extended period of time has passed (typically seven to fourteen days). Of course, cookies are the only method of persisting information between sessions, so persistent logins are implemented with a cookie that, at least temporarily, plays the role of authentication credentials. Improperly implemented, this can be a security disaster. In particular, storing a user's username and password in the cookie is a critical security breach.

So what is the secure way to implement persistent login? Well, there isn't a secure way to implement persistent login, but there are ways that are more secure than others. Best practice is to create an identifier corresponding to the username specifically for the authentication cookie, as well as a one-time authentication token that takes the place of the password, using code like the following:

$salt = 'mysalt'; /* replace mysalt with a string unique to your app */
$persist_user_id = md5($salt . md5($username . $salt));
$persist_auth_token = md5(uniqid(rand(), TRUE));
Make sure you enforce persistent login timeouts on the server, and regenerate the one-time token after every successful authentication (i.e., make sure that it is a one-time token). If a user explicitly logs out, be sure to explicitly "delete" the authentication cookie by setting its value to deleted or the like.

Image credit: tomswift46

PHP Security Basics: The Golden Rules

| February 24, 2009
Filter input. Escape output. You've heard it before, and you'll certainly hear it again. The reason is that rigorous application of these two rules can eliminate 80% of PHP security issues. There is a little bit of a culture clash here. One of the early attractions of PHP were features like register_globals, that did away with masses of parsing code. But our collective innocence has been lost. The register_globals directive has been set Off as a default since PHP version 4.2.0 and should remain that way. There is a lesson here. Explicit filtering of input is a habit that serious web application developers must adopt. If your application provides any value at all, attackers will try to exploit any holes you leave.

So what is input and what is filtering? Input is any incoming data that may be manipulated by an attacker. Obviously data coming from the client qualifies as input, e.g., data accessible via the _GET or _POST superglobals. (You may not trust client-side Javascript code, since it is trivially bypassed). Less obviously, several elements of the _SERVER superglobal can be set by the client. In fact, it is a good idea to consider the entire _SERVER superglobal array as input requiring filtering. Certainly files read from the filesystem should be considered input. Should data coming from the database be considered as input? While the rigorous answer is "yes", particularly if the database server is located remotely from the web server, for many applications it is not unreasonable to tie application security to database security. In this case, a judgment call is required. Such a judgment should be made carefully since it may limit the long term potential of an application.

Input becomes a PHP variable value. Input filtering is about ensuring that the variable value conforms to programmatic expectations. For some types of variable - integers, dates, phone numbers, credit card numbers, email addresses, URLs, etc - these expectations are well defined. However, part of what makes each application unique is the variable types it defines, implicitly or explicitly, so that input filtering is not as straightforward as it might seem. In addition, user expectations play a role. It is safest to require that usernames consist entirely of alphanumeric characters, but many systems also allow underscores, periods and dashes. More and more systems allow spaces, and disallowing single quotes is sure to annoy the O'Reillys of the world. It is possible to cater for all these cases. The point is to set programmatic expectations for variable values, and then ensure that those expectations are met before the variables are used.

By the way, don't try to modify input so that it conforms with expectations. This just introduces a layer of complexity that can itself easily result in new security vulnerabilities. Provide feedback to the application user about input expectations and simply require that they comply. Again, it is not unwise to enhance application functionality to deal with reasonable input, but once expectations have been set, simply require compliance.

Escaping output is typically much more straight forward. Some characters or character sequences have special meaning for the applications to which you send output. The standard examples are HTML sequences for the client and SQL sequences for the database. Of course you'll want to send various command sequences to the client and the database, but you'll almost never want the "active" parts of those sequences to come from inside your application variables. When you do, it should be very explicit, and even then carefully controlled. For the standard examples, use htmlentities for variables you're sending to the client and the equivalent of mysql_real_escape_string for your database. If your database doesn't have a vendor specific string escape function, you should write one. Since writing such a function can require considerable research, you can use addslashes as a fallback, but you should be aware that you will be vulnerable to vendor specific attacks.

Image credit: Pieter Musterd

PHP Security Basics: Shared Hosting (Part 2)

| February 22, 2009
We're discussing PHP specific vulnerabilities that are exacerbated by shared hosting environments. In the previous post, PHP Security Basics: Shared Hosting (Part 1), we discussed a source code exposure vulnerability and mitigation measures. In this post, we'll look at session data exposure and modification vulnerabilities.

By default, PHP stores session data as files in /tmp. These files have a simple filename structure and the contents can be conveniently decoded with session_decode. Other users on a shared host do not have direct read access to these files, but again, it is possible to co-opt a shared web server into exposing their contents. In fact, it isn't much of an effort to take the next step and modify the exposed session data, re-encode it with session_encode and overwrite the corresponding file in the /tmp directory. With this power, an attacker is not far away from giving themselves arbitrary access to your data.

The solution? Once again we resort to the database. That is, using the database as the session data store. This might seem laborious, but the PHP function session_set_save_handler makes the change fairly painless. All the actual database interaction should be contained in the six functions supplied to session_set_save_handler. The _SESSION variable can then be used in the normal way. The linked documentation page contains sample code for implementing the required database interaction.

Yes, storing session data in the database entails a performance hit, but this seems a small price to pay to check a critical security vulnerability in the form of arbitrary session data exposure and modification. Although most database scalability issues are solvable, in the case that a particular application installation must meet stringent performance requirements, it is likely that a dedicated hosting environment is available. Such installations always have the option of disabling use of the database as a session data store. However, use of the database as a session store is a wise default.

Image credit: CarbonNYC

PHP Security Basics: Shared Hosting (Part 1)

| February 21, 2009
PHP has an undeservedly poor reputation for security. While it is true that PHP doesn't force programmers to use secure practices, this is also true of most other programming languages. PHP programmers should view security as both a challenge and an opportunity. A challenge because no one except the attacker is happy when sensitive data is exposed. An opportunity because the ever shifting nature of security threats means that this is not an area that is going to be automated any time soon. Security is an area for PHP programmers to display skill and build a valuable reputation.

Server security is a prerequisite for all else, but generally a PHP programmer's responsibility in this area is going to be limited to choosing secure passwords, using ssh and sftp, and using high privilege accounts only when necessary. That said, programmers developing for mass markets would be wise to keep in mind that their application will likely reside on shared hosting at some point. The security of shared hosting can range from good to appalling, but it is never going to earn 5 stars. It is a simple fact that the number and variety of attack vectors are an order of magnitude higher on shared hosting, particularly if the server administrator doesn't take steps to harden the various default installations.

So what needs to be done to accommodate shared hosting environments? You probably aren't going to like the answer, so let me scare you a little bit first. In non-VPS shared hosting environments, you should assume that an attacker has full access to your PHP source code, including any database authentication credentials if, as is typical, you set those in a PHP file. Of course, even on dedicated servers, your PHP source code should be stored outside the directory tree that is published by the web server, but these files still need to be accessible to the PHP-enabled web server, so in a shared hosting environment, an attacker may be able co-opt the web server into exposing them. There are hosting-side measures that can be taken to mitigate such risks (chroot comes to mind), but the point is to stay out of the business of predicting or dictating user hosting environment requirements. Defense-in-depth is never a bad idea either. Even the best hosting environments can be compromised. In fact, it is the best hosting environments, along with their high value applications and databases that are most likely to be targeted by an attacker.

So what's the solution? There isn't a good solution, but the best option in a shared hosting environment is to store your source code in the database. And the database authentication credentials? Again, there isn't a good solution, but the best option is to store the credentials in environment variables that are available only to your instance of the web server. If you choose this option, you must be quite careful not to make the contents of _SERVER public, for example, via phpinfo.

Even if you're willing to trust hosting-side solutions for this particular vulnerability (i.e., source code exposure), shared hosting also makes your session data vulnerable to exposure and even modification by an attacker. We'll look at the problem and solutions next post.

Image credit: B Tal