PHP's session_set_save_handler: Easy to Get Things Wrong -------------------------------------------------------- SHH #6, 2003-05-13 Target Audience --------------- * Programmers implementing custom session save handlers in PHP * Testers auditing PHP applications Description ----------- First: This is not a vulnerability in PHP itself, but one of those design decision that makes it easy to write vulnerable PHP applications. PHP allows outside users to dictate the session ID by manipulating the PHPSESSID cookie parameter. When a custom made session save handler is used, the incoming ID is not validated by PHP. PHP thus passes a string of any length, containing any character to the custom made handler functions. If the programmer writing the session save handler is not aware that the ID may be anything, the application may become vulnerable to different kinds of attacks depending on how the session is persisted. Details ------- PHP normally creates session IDs consisting of limited-length strings of hexadecimal characters. The IDs are used as keys by the session save handler when it stores and looks up sessions. When a session is created, PHP normally instructs the visiting browser to remember a cookie--often called PHPSESSID--containing the session ID. When the browser later returns with a new request, PHP extracts the session ID from the incoming cookie and passes it to the session save handler. The save handler uses the ID to look up a matching session object. By default, PHP handles the saving of sessions internally. The default save handler stores session data in files on the server. A programmer may, however, install his own session save handler using a function called "session_set_save_handler". This function allows the programmer to register a set of callback functions that PHP will invoke in order to retrieve and store the session data. A programmer may e.g. write callback functions to save session data to a database. PHP has an obscure feature that lets a visitor dictate the session ID by modifying the value of PHPSESSID. Unfortunately, this feature is not mentioned in the documentation for "session_set_save_handler". A programmer reading the docs may easily think that the session ID he receives from PHP is that usual limited-length string of hexadecimal characters. That assumption is wrong, and assuming so could be dangerous. As an example, review the following Cookie header (white space inserted for readability): Cookie: PHPSESSID=%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F %3A%3B%3C%3E%3F%40%5B%5C%5D%60%7B%7C%7D%7E The header makes the save handler's "read" and "write" (and possibly "destroy") functions receive the following session ID parameter from PHP: !"#$%&'()*+,-./:;<>?@[\]`{|}~ This is exactly the string one will get by URL decoding the session ID from the Cookie header above. If magic_quotes_gpc is On, some of the characters will have a backslash in front of them: !\"#$%&\'()*+,-./:;<>?@[\\]`{|}~ The backslashes may or may not prevent attacks, depending on how the ID is parsed in the end. Is this problem just theoretical? It appears not to be. When searching Google for `php sql "session_set_save_handler"', one will find several implementations of database-saving session handlers. The first four on Google's list (as of this writing) appear to be vulnerable to SQL Injection through session cookie manipulation. At least one popular PHP-based publishing framework seems to be vulnerable as well. Solution -------- Anyone writing their own session save handler should make sure the session ID received from PHP is valid, for any definition of the word. They should also escape any metacharacters before passing the ID on. Vendor Notification ------------------- On 2003-02-18 the PHP developers were contacted about the ability for an outside user to dictate the session ID. They describe the ability as a feature that may be needed by some. Affected versions ----------------- The section title sounds somewhat misleading, as this advisory does not describe a bug. Testing was performed on PHP 4.3.2RC2. ---------------------------- Reported by Sverre H. Huseby