PHP Session File Hacks

If you want to learn best tricks and tips, there's only one way to do it... at least only one way that I know of. Here are some notes I created while learning about the intricacies of php sessions, it's all in the code.

; Handler used to store/retrieve data.
session.save_handler = files

Argument passed to save_handler. In the case of files, this is the path where data files are stored. As of PHP 4.0.1, you can define the path as:

session.save_path = "N;/path"

where N is an integer. Instead of storing all the session files in /path, what this will do is use subdirectories N-levels deep, and store the session data in those directories. This is useful if you or your OS have problems with lots of files in one directory, and is a more efficient layout for servers that handle lots of sessions.

; NOTE 1: PHP will not create this directory structure automatically.
;         You can use the script in the ext/session dir for that purpose.
; NOTE 2: See the section on garbage collection below if you choose to
;         use subdirectories for session storage
; The file storage module creates files using mode 600 by default.
; You can change that by using
;     session.save_path = "N;MODE;/path"
; where MODE is the octal representation of the mode. Note that this
; does not overwrite the process's umask.
;session.save_path = "/tmp"


/* {{{ PHP_INI
        STD_PHP_INI_BOOLEAN("session.bug_compat_42",    "1",         PHP_INI_ALL, OnUpdateBool,   bug_compat,         php_ps_globals,    ps_globals)
        STD_PHP_INI_BOOLEAN("session.bug_compat_warn",  "1",         PHP_INI_ALL, OnUpdateBool,   bug_compat_warn,    php_ps_globals,    ps_globals)
        STD_PHP_INI_ENTRY("session.save_path",          "",          PHP_INI_ALL, OnUpdateSaveDir,save_path,          php_ps_globals,    ps_globals)
        STD_PHP_INI_ENTRY("",               "PHPSESSID", PHP_INI_ALL, OnUpdateString, session_name,       php_ps_globals,    ps_globals)
        PHP_INI_ENTRY("session.save_handler",           "files",     PHP_INI_ALL, OnUpdateSaveHandler)
        STD_PHP_INI_BOOLEAN("session.auto_start",       "0",         PHP_INI_ALL, OnUpdateBool,   auto_start,         php_ps_globals,    ps_globals)
        STD_PHP_INI_ENTRY("session.gc_probability",     "1",         PHP_INI_ALL, OnUpdateLong,    gc_probability,     php_ps_globals,    ps_globals)
        STD_PHP_INI_ENTRY("session.gc_divisor",         "100",       PHP_INI_ALL, OnUpdateLong,    gc_divisor,        php_ps_globals,    ps_globals)
        STD_PHP_INI_ENTRY("session.gc_maxlifetime",     "1440",      PHP_INI_ALL, OnUpdateLong,    gc_maxlifetime,     php_ps_globals,    ps_globals)
        PHP_INI_ENTRY("session.serialize_handler",      "php",       PHP_INI_ALL, OnUpdateSerializer)
        STD_PHP_INI_ENTRY("session.cookie_lifetime",    "0",         PHP_INI_ALL, OnUpdateLong,    cookie_lifetime,    php_ps_globals,    ps_globals)
        STD_PHP_INI_ENTRY("session.cookie_path",        "/",         PHP_INI_ALL, OnUpdateString, cookie_path,        php_ps_globals,    ps_globals)
        STD_PHP_INI_ENTRY("session.cookie_domain",      "",          PHP_INI_ALL, OnUpdateString, cookie_domain,      php_ps_globals,    ps_globals)
        STD_PHP_INI_BOOLEAN("session.cookie_secure",    "",          PHP_INI_ALL, OnUpdateBool,   cookie_secure,      php_ps_globals,    ps_globals)
        STD_PHP_INI_BOOLEAN("session.cookie_httponly",  "",          PHP_INI_ALL, OnUpdateBool,   cookie_httponly,    php_ps_globals,    ps_globals)
        STD_PHP_INI_BOOLEAN("session.use_cookies",      "1",         PHP_INI_ALL, OnUpdateBool,   use_cookies,        php_ps_globals,    ps_globals)
        STD_PHP_INI_BOOLEAN("session.use_only_cookies", "0",         PHP_INI_ALL, OnUpdateBool,   use_only_cookies,   php_ps_globals,    ps_globals)
        STD_PHP_INI_ENTRY("session.referer_check",      "",          PHP_INI_ALL, OnUpdateString, extern_referer_chk, php_ps_globals,    ps_globals)
        STD_PHP_INI_ENTRY("session.entropy_file",       "",          PHP_INI_ALL, OnUpdateString, entropy_file,       php_ps_globals,    ps_globals)
        STD_PHP_INI_ENTRY("session.entropy_length",     "0",         PHP_INI_ALL, OnUpdateLong,    entropy_length,     php_ps_globals,    ps_globals)
        STD_PHP_INI_ENTRY("session.cache_limiter",      "nocache",   PHP_INI_ALL, OnUpdateString, cache_limiter,      php_ps_globals,    ps_globals)
        STD_PHP_INI_ENTRY("session.cache_expire",       "180",       PHP_INI_ALL, OnUpdateLong,    cache_expire,       php_ps_globals,    ps_globals)
        PHP_INI_ENTRY("session.use_trans_sid",          "0",         PHP_INI_ALL, OnUpdateTransSid)
        STD_PHP_INI_ENTRY("session.hash_function",      "0",         PHP_INI_ALL, OnUpdateLong,    hash_func,          php_ps_globals,    ps_globals)
        STD_PHP_INI_ENTRY("session.hash_bits_per_character",      "4",         PHP_INI_ALL, OnUpdateLong,    hash_bits_per_character,          php_ps_globals,    ps_globals)

        /* Commented out until future discussion */
        /* PHP_INI_ENTRY("session.encode_sources", "globals,track", PHP_INI_ALL, NULL) */
/* }}} */

Session Errors

The session id contains illegal characters, valid characters are a-z, A-Z, 0-9 and '-,'
fcntl(%d, F_SETFD, FD_CLOEXEC) failed: %s (%d)
open(%s, O_RDWR) failed: %s (%d)
ps_files_cleanup_dir: opendir(%s) failed: %s (%d)
read failed: %s (%d)
read returned less bytes than requested
write failed: %s (%d)
write wrote less bytes than requested
mm_malloc failed, avail %d, err %s
cannot allocate new data segment
Skipping numeric key %ld.
A session is active. You cannot change the session module's ini settings at this time.
Cannot find save handler %s
Cannot find serialization handler %s
Unknown session.serialize_handler. Failed to encode session object.
Cannot encode non-existent session.
Unknown session.serialize_handler. Failed to decode session object.
Failed to decode session object. Session has been destroyed.
Invalid session hash function
The ini setting hash_bits_per_character is out of range (should be 4, 5, or 6) - using 4 for now
No storage module chosen - failed to initialize session.
Failed to initialize storage module: %s (path: %s)
The session bug compatibility code will not
Your script possibly relies on a session side-effect which existed until PHP 4.2.3. Please be advised that the session extension does not consider global variables as a source of data, unless register_globals is enabled. You can disable this functionality and this warning by setting session.bug_compat_42 or session.bug_compat_warn to off, respectively.
Failed to write session data (%s). Please
Cannot send session cache limiter - headers already sent (output started at %s:%d)
Cannot send session cache limiter - headers already sent
Cannot send session cookie - headers already sent by (output started at %s:%d)
Cannot send session cookie - headers already sent
Cannot find save handler %s
Cannot find unknown save handler
purged %d expired session objects
Trying to destroy uninitialized session
Session object destruction failed
Cannot find named PHP session module (%s)
Argument %d is not a valid callback
Cannot regenerate session id - headers already sent
Session object destruction failed

        /* we don't perform any cleanup, if dirdepth is larger than 0.
           we return SUCCESS, since all cleanup should be handled by
           an external entity (i.e. find -ctime x | xargs rm) */

        if (data->dirdepth == 0) {
                *nrdels = ps_files_cleanup_dir(data->basedir, maxlifetime TSRMLS_CC);

        return SUCCESS;


/* If you change the logic here, please also update the error message in
 * ps_files_open() appropriately */
static int ps_files_valid_key(const char *key)
        size_t len;
        const char *p;
        char c;
        int ret = 1;

        for (p = key; (c = *p); p++) {
                /* valid characters are a..z,A..Z,0..9 */
                if (!((c >= 'a' && c <= 'z')
                                || (c >= 'A' && c <= 'Z')
                                || (c >= '0' && c <= '9')
                                || c == ','
                                || c == '-')) {
                        ret = 0;

        len = p - key;

        if (len == 0) {
                ret = 0;

        return ret;
static int ps_files_cleanup_dir(const char *dirname, int maxlifetime TSRMLS_DC)
        DIR *dir;
        char dentry[sizeof(struct dirent) + MAXPATHLEN];
        struct dirent *entry = (struct dirent *) &dentry;
        struct stat sbuf;
        char buf[MAXPATHLEN];
        time_t now;
        int nrdels = 0;
        size_t dirname_len;

        dir = opendir(dirname);
        if (!dir) {
                php_error_docref(NULL TSRMLS_CC, E_NOTICE, "ps_files_cleanup_dir: opendir(%s) failed: %s (%d)", dirname, strerror(errno), errno);
                return (0);


        dirname_len = strlen(dirname);

        /* Prepare buffer (dirname never changes) */
        memcpy(buf, dirname, dirname_len);
        buf[dirname_len] = PHP_DIR_SEPARATOR;

        while (php_readdir_r(dir, (struct dirent *) dentry, &entry) == 0 && entry) {
                /* does the file start with our prefix? */
                if (!strncmp(entry->d_name, FILE_PREFIX, sizeof(FILE_PREFIX) - 1)) {
                        size_t entry_len = strlen(entry->d_name);

                        /* does it fit into our buffer? */
                        if (entry_len + dirname_len + 2 < MAXPATHLEN) {
                                /* create the full path.. */
                                memcpy(buf + dirname_len + 1, entry->d_name, entry_len);

                                /* NUL terminate it and */
                                buf[dirname_len + entry_len + 1] = '�';

                                /* check whether its last access was more than maxlifet ago */
                                if (VCWD_STAT(buf, &sbuf) == 0 &&
#ifdef NETWARE
                                                (now - sbuf.st_mtime.tv_sec) > maxlifetime) {
                                                (now - sbuf.st_mtime) > maxlifetime) {


        return (nrdels);


#define PS_FILES_DATA ps_files *data = PS_GET_MOD_DATA()

        ps_files *data;
        const char *p, *last;
        const char *argv[3];
        int argc = 0;
        size_t dirdepth = 0;
        int filemode = 0600;

        if (*save_path == '�') {
                /* if save path is an empty string, determine the temporary dir */
                save_path = php_get_temporary_directory();

                if (strcmp(save_path, "/tmp")) {
                        if (PG(safe_mode) && (!php_checkuid(save_path, NULL, CHECKUID_CHECK_FILE_AND_DIR))) {
                                return FAILURE;
                        if (php_check_open_basedir(save_path TSRMLS_CC)) {
                                return FAILURE;

        /* split up input parameter */
        last = save_path;
        p = strchr(save_path, ';');
        while (p) {
                argv[argc++] = last;
                last = ++p;
                p = strchr(p, ';');
                if (argc > 2) break;
        argv[argc++] = last;

        if (argc > 1) {
                errno = 0;
                dirdepth = (size_t) strtol(argv[0], NULL, 10);
                if (errno == ERANGE) {
                        php_error(E_WARNING, "The first parameter in session.save_path is invalid");
                        return FAILURE;

        if (argc > 2) {
                errno = 0;
                filemode = strtol(argv[1], NULL, 8);
                if (errno == ERANGE || filemode < 0 || filemode > 07777) {
                        php_error(E_WARNING, "The second parameter in session.save_path is invalid");
                        return FAILURE;
        save_path = argv[argc - 1];

        data = emalloc(sizeof(*data));
        memset(data, 0, sizeof(*data));

        data->fd = -1;
        data->dirdepth = dirdepth;
        data->filemode = filemode;
        data->basedir_len = strlen(save_path);
        data->basedir = estrndup(save_path, data->basedir_len);


        return SUCCESS;
[PHP 5.2.0 session.save_path safe_mode and open_basedir bypass]

Author: Maksymilian Arciemowicz (SecurityReason)
- - Written: 02.10.2006
- - Public: 08.12.2006
SecurityAlert Id: 43
CVE: CVE-2006-6383
SecurityRisk: High
Affected Software: PHP 5.2.0
Advisory URL:

- --- 0.Description ---
PHP is an HTML-embedded scripting language. Much of its syntax is borrowed from
C, Java and Perl with a couple of unique PHP-specific features thrown in. The
goal of the language is to allow web developers to write dynamically generated
pages quickly.

A nice introduction to PHP by Stig Sather Bakken can be found at on the Zend website. Also, much  of the
PHP Conference Material is freely available.

Session support in PHP consists of a way to preserve certain data across
subsequent accesses. This enables you to build more customized applications and
increase the appeal of your web site.

A visitor accessing your web site is assigned a unique id, the so-called
session id. This is either stored in a cookie on the user side or is propagated
in the URL.

session.save_path defines the argument which is passed to the save handler. If
you choose the default files handler, this is the path where the files are
created. Defaults to /tmp. See also session_save_path().

There is an optional N argument to this directive that determines the number of
directory levels your session files will be spread around in. For example,
setting to '5;/tmp' may end up creating a session file and location like
/tmp/4/b/1/e/3/sess_4b1e384ad74619bd212e236e52a5a174If . In order to use N you
must create all of these directories before use. A small shell script exists in
ext/session to do this, it's called Also note that if N is used
and greater than 0 then automatic garbage collection will not be performed, see
a copy of php.ini for further information. Also, if you use N, be sure to
surround session.save_path in "quotes" because the separator (;) is also used
for comments in php.ini.

- --- 1. session.save_path safe mode and open basedir bypass ---
session.save_path can be set in ini_set(), session_save_path() function. In
session.save_path there must be path where you will save yours tmp file. But
syntax for session.save_path can be:




N - can be a string.


1. session_save_path("/DIR/WHERE/YOU/HAVE/ACCESS")
2. session_save_path("5;/DIR/WHERE/YOU/HAVE/ACCESS")


        char buf[MAX_STR + 1];
        struct timeval tv;
        time_t now;

        gettimeofday(&tv, NULL);
        now = tv.tv_sec + PS(cache_expire) * 60;
#define EXPIRES "Expires: "
        memcpy(buf, EXPIRES, sizeof(EXPIRES) - 1);
        strcpy_gmt(buf + sizeof(EXPIRES) - 1, &now);

        snprintf(buf, sizeof(buf) , "Cache-Control: public, max-age=%ld", PS(cache_expire) * 60); /* SAFE */


        char buf[MAX_STR + 1];

        snprintf(buf, sizeof(buf), "Cache-Control: private, max-age=%ld, pre-check=%ld", PS(cache_expire) * 60, PS(cache_expire) * 60); /* SAFE */


        ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");

        ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");
        /* For HTTP/1.1 conforming clients and the rest (MSIE 5) */
        ADD_HEADER("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
        /* For HTTP/1.0 conforming clients */
        ADD_HEADER("Pragma: no-cache");

static php_session_cache_limiter_t php_session_cache_limiters[] = {

static int php_session_cache_limiter(TSRMLS_D)
        php_session_cache_limiter_t *lim;

        if (PS(cache_limiter)[0] == '�') return 0;

        if (SG(headers_sent)) {
                char *output_start_filename = php_get_output_start_filename(TSRMLS_C);
                int output_start_lineno = php_get_output_start_lineno(TSRMLS_C);

                if (output_start_filename) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot send session cache limiter - headers already sent (output started at %s:%d)",
                                output_start_filename, output_start_lineno);
                } else {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot send session cache limiter - headers already sent");
                return -2;

        for (lim = php_session_cache_limiters; lim->name; lim++) {
                if (!strcasecmp(lim->name, PS(cache_limiter))) {
                        return 0;

        return -1;
static void php_session_send_cookie(TSRMLS_D)
        smart_str ncookie = {0};
        char *date_fmt = NULL;
        char *e_session_name, *e_id;

        if (SG(headers_sent)) {
                char *output_start_filename = php_get_output_start_filename(TSRMLS_C);
                int output_start_lineno = php_get_output_start_lineno(TSRMLS_C);

                if (output_start_filename) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot send session cookie - headers already sent by (output started at %s:%d)",
                                output_start_filename, output_start_lineno);
                } else {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot send session cookie - headers already sent");

        /* URL encode session_name and id because they might be user supplied */
        e_session_name = php_url_encode(PS(session_name), strlen(PS(session_name)), NULL);
        e_id = php_url_encode(PS(id), strlen(PS(id)), NULL);

        smart_str_appends(&ncookie, COOKIE_SET_COOKIE);
        smart_str_appends(&ncookie, e_session_name);
        smart_str_appendc(&ncookie, '=');
        smart_str_appends(&ncookie, e_id);


        if (PS(cookie_lifetime) > 0) {
                struct timeval tv;
                time_t t;

                gettimeofday(&tv, NULL);
                t = tv.tv_sec + PS(cookie_lifetime);

                if (t > 0) {
                        date_fmt = php_std_date(t TSRMLS_CC);
                        smart_str_appends(&ncookie, COOKIE_EXPIRES);
                        smart_str_appends(&ncookie, date_fmt);

        if (PS(cookie_path)[0]) {
                smart_str_appends(&ncookie, COOKIE_PATH);
                smart_str_appends(&ncookie, PS(cookie_path));

        if (PS(cookie_domain)[0]) {
                smart_str_appends(&ncookie, COOKIE_DOMAIN);
                smart_str_appends(&ncookie, PS(cookie_domain));

        if (PS(cookie_secure)) {
                smart_str_appends(&ncookie, COOKIE_SECURE);

        if (PS(cookie_httponly)) {
                smart_str_appends(&ncookie, COOKIE_HTTPONLY);


        /*      'replace' must be 0 here, else a previous Set-Cookie
                header, probably sent with setcookie() will be replaced! */
        sapi_add_header_ex(ncookie.c, ncookie.len, 0, 0 TSRMLS_CC);

PHP PHP Session Session ID