In this series, I started with techniques for securing user input, then extended those techniques to applications where database input and output is required, looking at some SQL security issues. I then moved on to deal with file operations and file uploads, looking specifically at the security issues involved with accessing files based on some user supplied filename, and user-supplied files (uploaded files). In part 5, I discussed the PHP Safe Mode in some detail.
In this sixth, and final, part of the series, I will cover session management, and conclude with a brief look at security modules for Apache, and multiple server instances.
What Are Sessions?
Sessions are a PHP construct allowing persistent data to be retained across HTTP connections. In English, sessions allow you to store the values of certain variables across page visits. This is achieved by serializing the data (converting it to some binary representation) and writing it out to a file (or a database, or wherever you tell it), when a page is finished processing in PHP. When the next page (or that same page some time later) is processed, and PHP is told to start a session, it will check if the user already has a session, and read their data back in, unserializing it and assigning the variables. This allows you to keep track of a user across multiple visits, or while browsing multiple pages on your site.
For example, you can create a shopping cart using sessions, storing an array of items added to the cart in a session variable, and loading it on every page. When the user clicks 'Add to cart' you can add the item to the array, and it will be saved for the next page the user goes to. The whole array can be fetched on your checkout page and appropriate processing will take place.
How Do Sessions Work?
As many probably know, HTTP is a stateless protocol. By stateless, I mean that any HTTP connection is unaware of previous connections made by the same client, to the same server (persistent connections excepting). There are two useful ways in which PHP can pass identification information between pages in order to uniquely associate a user with a session.
Passing session data in URLs is not recommended since it is possible to pass your session onto another user if you give them a link which contains your session ID, and the session ID data is more easily attackable than in a cookie. URL-based session tracking should be used only where cookies cannot.
PHP provides a super-global variable named $_SESSION. By super-global I mean it is a global variable which you may access without going via $_GLOBALS or stating global $_SESSION within a function. In this way, it behaves like $_GET and $_POST.
$_SESSION is, in fact, an associative array. The keys are variable names, and the values are the stored session data for that variable name.
Using $_SESSION is preferred over the use of session_register() to register ordinary global variables as session variables, especially when register_globals is enabled, since global variables may be more easily changed inadvertently than the contents of $_SESSION. It is still possible to alias ordinary global variables to their equivalents within $_SESSION,
$username = &$_SESSION["username"];
Here, the & indicates a reference, or alias. It is then possible to use $username instead of $_SESSION["username"], but note that $username is an ordinary variable, and you will have to access as $_GLOBALS["username"] or global $username from within a function.
Trusting Session Data
Since a session ID can be spoofed, it is always wise to perform some extra validation where possible. The simplest mechanism would be to store the IP address of the client to whom the session ID was issued, and compare the client IP against that stored IP every session. This will prevent the basic security problems associated with passing links between computers (though not if the computers are on a private network and share a single public IP address).
Session data is also stored in files on the server. The default location is /tmp on UNIX, or the system temporary file directory on Windows. If /tmp is world-writable (or, in some cases, world-readable), or there are multiple websites hosted on a single server, storing session data in a public location is not secure. PHP provides a way to change the way session data is stored.
Changing The Session File Path
The location in which PHP saves session data can be set using the php.ini directive session.save_path, or the string below in httpd.conf or a virtual host configuration.
php_value session.save_path "/home/andrew/sessions/"
It is important to ensure that your session data path is included in the paths allowed by open_basedir, if you have open_basedir settings or PHP Safe Mode enabled.
The data representation used for saving session data to files can be controlled with the session.serialize_handler directive in php.ini. By default, PHP uses its own built in format, but the WDDX ( http://www.wddx.org ) format can be used also. Set the type using one of the lines below.
(in php.ini ...)
(or in httpd.conf ...)
php_value session.serialize_handler wddx
php_value session.serialize_handler php
Storing Session Data In A Database
When you use on-disk files to store session data, those files must be readable and writeable by PHP. On a multi-user hosting system, it is possible for other users to access your session data through the PHP process (but see the commentary on open_basedir in part 5 of this series. The best way to secure your session data is to store it in a database.
Unfortunately, there is no direct way to store session data in a database using the php.ini directives, but luckily PHP provides a mechanism for customised session storage handlers. The function session_set_save_handler() allows you to register handler functions for session management. These functions must be written in PHP (or made available as a PHP extension).
session_set_save_handler(open_fn, close_fn, read_fn, write_fn,
To use these user-supplied session storage handlers, you must set session.save_handler to the value user, and the value of session.save_path should be the name of the database into which you're saving session data (so that the session save handler functions you define can locate and use that database). The value of session.name can be used as the name of the table within the database.
php_value session.save_handler user
php_value session.save_path dbname
php_value session.name session_data
Next, a table for storing session data must exist in the database. At the minimum, your session handler should keep track of the session ID, the expiration time, and the serialized session data. The SQL below creates a simple table for storing this data.
CREATE TABLE session_data (
sessionid text not null PRIMARY KEY,
sessiondata text not null
The final task is to create the functions which manage this session store, and register them with session_set_save_handler(). The open_fn must open the database connection, the close_fn must close it and perform any associated cleanup tasks, and the read_fn and write_fn functions must read and write session data respectively. destroy_fn is called when a session ends and is destroyed, and gc_fn is called when session data is garbage collected. These operations must be mapped into database queries by your PHP code. The prototypes for the functions are given below, and parameters passed are explained.
function open_fn($save_path, $session_name)
$save_path is the value of session.save_path, $session_name is the value of session.name
Takes no arguments
function read_fn($session_id, $data)
$session_id is the session ID for which PHP requests the associated session data to be returned
$session_id is the session ID for which PHP requests that $data be associated with in the session store (database)
$session_id is the ID of a session which may be removed from the store
$max_time is the oldest last modified time to retain in the session store. Sessions with an older modified time than this are to be removed from the store.
Implementing the above functions, you are not limited simply to database connections. You could, for instance, connect to some other data storage application, or store the session data in an encrypted virtual filesystem, or on a network file server.
Further Securing Sessions
There are a few remaining PHP directives for controlling sessions, several of these have security implications. Firstly, the session name (set with session.name) should be changed from the default to avoid collisions, especially on servers with multiple users.
The session.cookie_path directive determines the default cookie path, the path for which cookies will be sent in an HTTP request. If you have a forum at somedomain.com/forum, and somedomain.com/ does not require session management, you can change session.cookie_path as shown below.
php_value session.cookie_path /forum/
This prevents sections of your site which do not require the session cookie from being sent it, and limits exposure of the session IDs to those parts of a site where sessions are actually being used. This is especially important if some sections of your site have pages provided by other users, who could use those pages to steal session IDs from your visitors.
Setting session.use_only_cookies to true disables the passing of session IDs in URLs, at the cost of losing sessions support for users with cookies disabled, or on browsers not supporting cookies. Setting session.cookie_domain to the most restrictive domain name possible (e.g. forum.somesite.com instead of somesite.com) also helps to minimise exposure of session IDs. Of course, if you have a single login for an entire range of subdomains, you will have to set the domain as somedomain.com to ensure that the sessions are correctly managed across all of the subdomains.
Finally, it is possible to set the hash function used when creating session IDs. The default is to use MD5 (hash function 0), but SHA1 may also be used (hash function 1). SHA1 is a 160-bit hash function, whereas MD5 is only a 128-bit hash function, so using SHA1 for session hashes improves security slightly over using MD5. You can set the hash function using This setting was introduced in PHP 5.
php_value session.hash_function 1
Beyond PHP Security
Everything I've covered so far has been directly related to PHP and SQL security. The best situation we can manage here is PHP Safe Mode, which uses self-imposed restrictions to improve security. That this is the best we can achieve is due to the server architecture currently in use. There are, however, a few options for taking security a little further, and imposing the restrictions at a lower level than PHP itself. To conclude this series, I'll mention some of these briefly here.
Chroot changes the "root" directory that a process can see. This effectively locks it into a certain directory structure within the overall filesystem. With this approach, you can lock a web server into some directory such as /home/www and it will not be able to access anything outside of that structure.
There are several advantages to doing this. The first is that the web server, PHP, any user scripts, and also any attackers, will be contained within this chroot "jail", unable to access files outside of it. Furthermore, you can remove all but the most essential software from the chroot environment. Removing any shells from the environment prevents a large number of exploits which attempt to invoke a remote shell. The minimal environment inside a chroot makes life very difficult for attackers, no matter whether their method of attack is through a vulnerability in your PHP code, or a vulnerability in the underlying web server.
Apache mod_security and mod_chroot
mod_security and mod_chroot are extension modules specifically for the Apache web server. These two modules provide chroot support for Apache without externally applying a chroot technique. mod_security also provides several other security features. Further information is available at http://www.modsecurity.org/ for mod_security and at http://core.segfault.pl/~hobbit/mod_chroot/ for mod_chroot.
suEXEC and Multiple Server Instances
Using a chroot to lock your web server into a restricted environment helps to prevent some security problems, but one of the big issues is shared hosting. Running multiple websites on the same server requires that the web server process has access to each user's files. If the web server has access, so do the other users (subject to PHP Safe Mode restrictions, of course). There are two ways around this, one which is Apache specific, and one which may be deployed on any server environment.
suEXEC, specific to Apache, switches an Apache process to be owned by the same user as the script it is executing, losing any escalated permissions. This locks that Apache instance into the permissions held by that user, rather than the permissions held by the master web server process itself. This mechanism allows a return to the more traditional permissions system, and each user can be reasonably sure his or her files are protected. The cost of this is that an Apache process may not then be promoted back to regain permissions and switch user again to serve a different user's files. This system works best when there will be many requests for pages owned by the same user. suEXEC is explained in more detail at http://httpd.apache.org/docs/1.3/suexec.html
The alternative is to use multiple instances of the web server, each one running with the permissions of a different user. Each server then only has the permissions it needs to serve a single website, so a reverse proxy must be used as a front to all of these server instances, redirecting requests for a virtually hosted website to the Apache instance responsible for actually serving that site. This solution is the most secure, but also the most resource-hungry. Information about using Apache as a reverse proxy is available at http://httpd.apache.org/docs/1.3/mod/mod_proxy.html
How to check for PHP vulnerabilities
The best way to check whether your web site & applications are vulnerable to PHP security attacks is by using a Web Vulnerability Scanner. A Web Vulnerability Scanner crawls your entire website and automatically checks for vulnerabilities to PHP attacks. It will indicate which scripts are vulnerable so that you can fix the vulnerability easily. Besides PHP security vulnerabilities, a web application scanner will also check for SQL injection, Cross site scripting & other web vulnerabilities.
Check if your website is vulnerable to attack with Acunetix Web Vulnerability Scanner
Acunetix Web Vulnerability Scanner ensures website security by automatically checking for SQL injection, Cross site scripting and other vulnerabilities. It checks password strength on authentication pages and automatically audits shopping carts, forms, dynamic content and other web applications. As the scan is being completed, the software produces detailed reports that pinpoint where vulnerabilities exist. Take a product tour or download the evaluation version today!
Scanning for XSS vulnerabilities with Acunetix WVS Trial Edition!
To check whether your website has cross site scripting vulnerabilities, download the Free Edition from here. This version will scan any website / web application for XSS vulnerabilities and it will also reveal all the essential information related to it, such as the vulnerability location and remediation techniques. Scanning for XSS is normally a quick exercise (depending on the size of the website).
This concludes the tour of PHP and SQL security. There is much more information available on the web about each security issue covered in this series, and many other issues also. In particular, several Apache modules exist for improving security through chrooting, reverse proxies, or simply checking requests for attacks such as directory traversal.
The PHP manual over at php.net points out any security concerns relating to most functions, and the comments posted by users below the reference pages often contain useful security hints and code samples.
I will leave you with this thought:
The key to good security is not to add it as an afterthought, but to plan your system with security in mind from the beginning.