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.
[Session] ; 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"
session.c
/* {{{ PHP_INI */ PHP_INI_BEGIN() 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("session.name", "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) */ PHP_INI_END() /* }}} */
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
PS_GC_FUNC(files) { PS_FILES_DATA; /* 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; }
mod_files.c
/* 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; break; } } 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); } time(&now); 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) { #else (now - sbuf.st_mtime) > maxlifetime) { #endif VCWD_UNLINK(buf); nrdels++; } } } } closedir(dir); return (nrdels); }
ext/session/mod_files.c
#define PS_FILES_DATA ps_files *data = PS_GET_MOD_DATA() PS_OPEN_FUNC(files) { 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); PS_SET_MOD_DATA(data); return SUCCESS; }
[PHP 5.2.0 session.save_path safe_mode and open_basedir bypass] Author: Maksymilian Arciemowicz (SecurityReason) Date: - - 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: http://securityreason.com/achievement_securityalert/43 Vendor: http://www.php.net - --- 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 http://www.zend.com/zend/art/intro.php 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 mod_files.sh. 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: [/PATH] OR [N;/PATH] N - can be a string. EXAMPLES: 1. session_save_path("/DIR/WHERE/YOU/HAVE/ACCESS") 2. session_save_path("5;/DIR/WHERE/YOU/HAVE/ACCESS") and 3. session_save_path("/DIR/WHERE/YOU/DONT/HAVE/ACCESS�;/DIR/WHERE/YOU/HAVE/ACCESS")
CACHE_LIMITER_FUNC(public) { 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); ADD_HEADER(buf); snprintf(buf, sizeof(buf) , "Cache-Control: public, max-age=%ld", PS(cache_expire) * 60); /* SAFE */ ADD_HEADER(buf); last_modified(TSRMLS_C); } CACHE_LIMITER_FUNC(private_no_expire) { 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(buf); last_modified(TSRMLS_C); } CACHE_LIMITER_FUNC(private) { ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT"); CACHE_LIMITER(private_no_expire)(TSRMLS_C); } CACHE_LIMITER_FUNC(nocache) { 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[] = { CACHE_LIMITER_ENTRY(public) CACHE_LIMITER_ENTRY(private) CACHE_LIMITER_ENTRY(private_no_expire) CACHE_LIMITER_ENTRY(nocache) {0} }; 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))) { lim->func(TSRMLS_C); 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"); } return; } /* 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); efree(e_session_name); efree(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); efree(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); } smart_str_0(&ncookie); /* '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); }