CodeIgniter伪随机数导致加密失效

首先来看一段CI内核在开启session储存在数据库选项的时候的操作.也就是

/system/core/config.php

内核配置文件设置为以下的时候

$config['sess_use_database']   = true;

/system/libraries/Session/session.php

``` public function __construct($params = array())

{

    log_message('debug', "Session Class Initialized");



    // Set the super object to a local variable for use throughout the class

    $this->CI =& get_instance();

    /*

    if (defined('ci_session')) {

        return $this->CI->session;

    } else {

        define('ci_session', 1);

    }

    */

    // Set all the session preferences, which can either be set

    // manually via the $params array above or via the config file



    foreach (array('sess_encrypt_cookie', 'sess_use_database', 'sess_table_name', 'sess_expiration', 'sess_expire_on_close', 'sess_match_ip', 'sess_match_useragent', 'sess_cookie_name', 'cookie_path', 'cookie_domain', 'cookie_secure', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key') as $key)

    {

        $this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key);

    }



    if ($this->encryption_key == '')

    {

        $this->encryption_key == 'finecms190';

    }



    // Load the string helper so we can use the strip_slashes() function

    $this->CI->load->helper('string');



    // Do we need encryption? If so, load the encryption class

    if ($this->sess_encrypt_cookie == TRUE)

    {

        $this->CI->load->library('encrypt');

    }



    // Are we using a database?  If so, load it

    if ($this->sess_use_database === TRUE AND $this->sess_table_name != '')

    {

        $this->CI->load->database();

    }



    // Set the "now" time.  Can either be GMT or server time, based on the

    // config prefs.  We use this to set the "last activity" time

    $this->now = $this->_get_time();



    // Set the session length. If the session expiration is

    // set to zero we'll set the expiration two years from now.

    if ($this->sess_expiration == 0)

    {

        $this->sess_expiration = (60*60*24*365*2);

    }



    // Set the cookie name

    $this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name;



    // Run the Session routine. If a session doesn't exist we'll

    // create a new one.  If it does, we'll update it.

    if ( ! $this->sess_read())

    {

        $this->sess_create();

    }

    else

    {

        $this->sess_update();

    }



    // Delete 'old' flashdata (from last request)

    $this->_flashdata_sweep();



    // Mark all new flashdata as old (data will be deleted before next request)

    $this->_flashdata_mark();



    // Delete expired sessions if necessary

    $this->_sess_gc();



    log_message('debug', "Session routines successfully run");

}

```

简单的用大白话来说,这段代码就是用来做一些对Session 的初始工作,检测配置,检测cookie是否设置等,检测配置最后有这个函数 sess gc().这是每次请求的时候都会运行到的。

``` function sess gc()

{

    if ($this->sess_use_database != TRUE)

    {

        return;

    }

    srand(time());

    if ((rand() % 100) < $this->gc_probability)

    {

        $expire = $this->now - $this->sess_expiration;



        $this->CI->db->where("last_activity < {$expire}");

        $this->CI->db->delete($this->sess_table_name);



        log_message('debug', 'Session garbage collection performed.');

    }

}

```

可以看到这里首先检测了是否开启了数据库储存session,如果开启就会以当前的时间设置一个种子。大家可能觉得没有什么问题

首先我先介绍一下一个特性,我们在发送HTTP给PHP-CGI的时候

这个请求对应的返回的时间戳,是php先生成的。

假如当前是 10:00

php代码是

``` echo "start";

sleep(5);

//Insert some code.

echo "pause";

sleep(10) ```

大家可能觉得返回的时间戳是请求接受到的时间+处理的时间。10:15

实际上应该就是请求接收到的时间。

为什么我要说这个呢?

因为CI的框架本身代码执行时间就是等于返回的时间戳的时间。所以在运行到

srand(time());

当种子设置为时间戳之后,之后所有的基于PRNG的函数全部变的可计算了。

举个例子:

FineCMS2.0.1有一个文件解压getshell漏洞

详情可以看

/bugs/wooyun-2010-064128

这篇文章。

官方的修复方案是将目录名称随机

$temp = APP_ROOT.'cache/attack/'.md5(uniqid().rand(0, 9999)).'/'

让攻击者不可猜测,从而即使攻击者上传了shell之后,也找不到执行的目录。

假设开启了数据库储存session选项之后。那么即使官方用了两个随机函数,一个加密函数,都变辣鸡。

我们先来看uniqid()的php的内核源码

``` PHP_FUNCTION(uniqid)

{

char *prefix = "";

if defined(CYGWIN)

zend_bool more_entropy = 1;

else

zend_bool more_entropy = 0;

endif

char *uniqid;

int sec, usec, prefix_len = 0;

struct timeval tv;



if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sb", &prefix, &prefix_len,

                          &more_entropy)) {

    return;

}

if HAVE USLEEP && !defined(PHP WIN32)

if (!more_entropy) {

if defined(CYGWIN)

php_error_docref(NULL TSRMLS_CC, E_WARNING, "You must use 'more entropy' under CYGWIN");

    RETURN_FALSE;

else

usleep(1);

endif

}

endif

gettimeofday((struct timeval *) &tv, (struct timezone *) NULL);

sec = (int) tv.tv_sec;

usec = (int) (tv.tv_usec % 0x100000);



/* The max value usec can have is 0xF423F, so we use only five hex

 * digits for usecs.

 */

if (more_entropy) {

    spprintf(&uniqid, 0, "%s%08x%05x%.8F", prefix, sec, usec, php_combined_lcg(TSRMLS_C) * 10);

} else {

    spprintf(&uniqid, 0, "%s%08x%05x", prefix, sec, usec);

}



RETURN_STRING(uniqid, 0);

}

endif

/ }}} /

``` 可以看到uniqid()在默认情况下会生成13位"随机"数字,实际上就是将当前的时间戳变为16进制,8位秒以上的16进制+5位微秒时间戳而已。呵呵没错,又是时间戳。

那么这时候就会变成,13位里面我们可以确定8位,剩下5位是我们不可见的。

然后rand()的值,我们知道了种子,可以列出到达这里的rand()的随机值[根据rand()调用的次数]

那么md5(uniqid().rand(0,9999))

这个加密方法,实际上就变成了跑5位数0-F字典的问题了。

如果说框架没有问题,想跑出来这个这个目录个人电脑要进行上亿次的请求,这几乎不可能。但是当框架帮我们固定了随机数种子之后,一切都变得so easy。

同理如果cookie生成,csrf生成用到了rand()函数,全部都会被击溃。

千里之堤,溃于蚁穴。

攻击者发送请求

根据服务器返回的时间,转为时间戳

根据时间戳计算加密过后的目录

服务器上攻击者攻击之后生成的目录

最后由 LonelyRain 编辑于2016年07月20日 11:41

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章