PHP的SESSIONID⽣成原理
作为⼀个web程序猿,我们对session肯定都不陌⽣,session id是我们各⾃在服务器上的⼀个唯⼀标志,这个id串既可以由php⾃动来⽣成,也可以由我们来赋予。你们可能和我⼀样,很关⼼php⾃动⽣成的那个id串是怎么来的,冲突的概率有多⼤,以我们下载⼀份php5.3.6的源码,进⼊/ext/session⽬录,⽣成session id的函数位于session.c⽂件的345⾏,下⾯详细介绍⼀下这个函数。为了⽅⾯理解,我调整了⼀些代码的顺序。
PHPAPI char *php_session_create_id(PS_CREATE_SID_ARGS) /* {{{ */
{
//这⼏⾏⾏定义了些散列函数所需的数据,直接越过~
PHP_MD5_CTX md5_context;
PHP_SHA1_CTX sha1_context;
#if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
void *hash_context;
#endif
unsigned char *digest;
int digest_len;
int j;
char *buf, *outid;
zval **array;
zval **token;
//⽤来记录$_SERVER['REMOTE_ADDR']的值
char *remote_addr = NULL;
//⼀个timeval结构,⽤来记录当前的时间戳及毫秒数
struct timeval tv;
gettimeofday(&tv, NULL);
//如果可能的话,就对remote_ADDR进⾏赋值,⽤php伪代码表⽰便是:
//if(isset($_SERVER['REMOTE_ADDR']))
//{remote_addr = $_SERVER['REMOTE_ADDR'];}
//备注:在cli模式下是没有的~
if (
zend_hash_find(
&EG(symbol_table),
"_SERVER",
sizeof("_SERVER"),
(void **) &array
)
== SUCCESS
&& Z_TYPE_PP(array) == IS_ARRAY
&& zend_hash_find(
Z_ARRVAL_PP(array),
"REMOTE_ADDR",
sizeof("REMOTE_ADDR"),
(void **) &token
) == SUCCESS
)
{
remote_addr = Z_STRVAL_PP(token);
php文件下载源码
}
/* maximum 15+19+19+10 bytes */
//⽣成所需的session id,当然后⾯还需要后续的处理~
//格式为:%.15s%ld%ld%0.8F,每⼀段的含义如下:
//%.15s    remote_addr ? remote_addr : "" 这⼀⾏很容易理解
//%ld        tv.tv_sec    当前的时间戳
//%ld        (long int)tv.tv_usec 当前毫秒数
//%0.8F    php_combined_lcg(TSRMLS_C) * 10 ⼀个随机数
spprintf(
&buf,
0,
"%.15s%ld%ld%0.8F",
remote_addr ? remote_addr : "",
tv.tv_sec,
(long int)tv.tv_usec,
php_combined_lcg(TSRMLS_C) * 10
);
//下⾯对buf字符串的值进⾏散列处理
//检测session配置中的散列函数
/*
300⾏:    enum{
PS_HASH_FUNC_MD5,
PS_HASH_FUNC_SHA1,
PS_HASH_FUNC_OTHER
};
812⾏:
PHP_INI_ENTRY("session.hash_function","0",PHP_INI_ALL,OnUpdateHashFunc)
738⾏:
static PHP_INI_MH(OnUpdateHashFunc)
{
......
......
val = strtol(new_value, &endptr, 10);
if (endptr && (*endptr == '\0'))
{
/* Numeric value */
PS(hash_func) = val ? 1 : 0;
return SUCCESS;
}
......
......
可知PS(hash_func)的默认值为0,即PS_HASH_FUNC_MD5。
*/
switch (PS(hash_func))
{
//如果是md5,则⽤md5算法对我们的buf串进⾏散列处理。
case PS_HASH_FUNC_MD5:
PHP_MD5Init(&md5_context);
PHP_MD5Update(&md5_context, (unsigned char *) buf, strlen(buf));
digest_len = 16;
break;
//如果是SHA1,则⽤SHA1算法对我们的buf串进⾏散列处理。
case PS_HASH_FUNC_SHA1:
PHP_SHA1Init(&sha1_context);
PHP_SHA1Update(&sha1_context, (unsigned char *) buf, strlen(buf));
digest_len = 20;
break;
#if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
case PS_HASH_FUNC_OTHER:
if (!PS(hash_ops))
{
php_error_docref(
NULL TSRMLS_CC,
E_ERROR,
"Invalid session hash function"
);
efree(buf);
return NULL;
}
hash_context = emalloc(PS(hash_ops)->context_size);
PS(hash_ops)->hash_init(hash_context);
PS(hash_ops)->hash_update(hash_context, (unsigned char *) buf, strlen(buf));
digest_len = PS(hash_ops)->digest_size;
break;
#endif /* HAVE_HASH_EXT */
//如果没有散列函数,则报错,还是E_ERROR级别的,囧~
default:
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Invalid session hash function");
efree(buf);
return NULL;
}
//释放buf~
//囧,那内容呢,内容已经去我们的hash_context⾥,⽐如md5_context、sha1_context。。。。。。
efree(buf);
/*
该资源将在会话 ID 创建进程中被⽤作附加的熵值资源。
例如在许多 Unix 系统下都可以⽤ /dev/random 或 /dev/urandom。
如果entropy_length这个配置⼤于0,则:
*/
if (PS(entropy_length) > 0)
{
#ifdef PHP_WIN32
unsigned char rbuf[2048];
size_t toread = PS(entropy_length);
if (php_win32_get_random_bytes(rbuf, (size_t) toread) == SUCCESS)
{
switch (PS(hash_func))
{
case PS_HASH_FUNC_MD5:
PHP_MD5Update(&md5_context, rbuf, toread);
break;
case PS_HASH_FUNC_SHA1:
PHP_SHA1Update(&sha1_context, rbuf, toread);
break;
# if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
case PS_HASH_FUNC_OTHER:
PS(hash_ops)->hash_update(hash_context, rbuf, toread);
break;
# endif /* HAVE_HASH_EXT */
}
}
#else
int fd;
fd = VCWD_OPEN(PS(entropy_file), O_RDONLY);
if (fd >= 0)
{
unsigned char rbuf[2048];
int n;
int to_read = PS(entropy_length);
while (to_read > 0) {
n = read(fd, rbuf, MIN(to_read, sizeof(rbuf)));
if (n hash_update(hash_context, rbuf, n);
break;
#endif /* HAVE_HASH_EXT */
}
to_read -= n;
}
close(fd);
}
//结束entropy_length>0时的逻辑
#endif
}
//还是散列计算的⼀部分,看来我们的hash_final(digest, hash_context);
efree(hash_context);
break;
#endif /* HAVE_HASH_EXT */
}
/*
session.hash_bits_per_character允许⽤户定义将⼆进制散列数据转换为可读的格式时每个字符存放多少个⽐特。
可能值为 '4'(0-9,a-f),'5'(0-9,a-v),以及 '6'(0-9,a-z,A-Z,"-",",")。
*/
if (PS(hash_bits_per_character) < 4
|| PS(hash_bits_per_character) > 6) {
PS(hash_bits_per_character) = 4;
php_error_docref(
NULL TSRMLS_CC,
E_WARNING,
"The ini setting hash_bits_per_character is out of range (should be 4, 5, or 6) - using 4 for now"
);
}
//将我们的散列后的⼆进制数据digest⽤字符串表⽰成可读的形式,并放置在outid字符串⾥
outid = emalloc((size_t)((digest_len + 2) * ((8.0f / PS(hash_bits_per_character)) + 0.5)));
j = (int) (bin_to_readable((char *)digest, digest_len, outid, (char)PS(hash_bits_per_character)) - outid);
efree(digest);
if (newlen) {
*newlen = j;
}
//返回outid
return outid;
}
所以我们可以得出结论,php的默认session_id⽣成算法还是⽐较随机的,除⾮攻击者都够同时猜中时间戳、毫秒数、后⾯的那个随机数。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。