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);
}

/* 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);

/*
session.entropy_file 给出了一个到外部资源(文件)的路径,
该资源将在会话 ID 创建进程中被用作附加的熵值资源。
例如在许多 Unix 系统下都可以用 /dev/random 或 /dev/urandom。
session.entropy_length 指定了从上面的文件中读取的字节数。默认为 0(禁用)。

如果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生成算法还是比较随机的,除非攻击者都够同时猜中时间戳、毫秒数、后面的那个随机数。

 

PHP的SESSIONID生成原理,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。