PHP的预定义变量:$_SERVER,$_POST,$_GET,$_COOKIE,$_ENV,$_FILES和$_REQUEST,这些变量的生成过程。
主要是由于之前看到一篇文章通过构造Hash冲突实现各种语言的拒绝服务攻击
看完之后思考这些变量是什么时候生成的,是由web服务器生成的还是PHP生成的?

猜想:

客户端将请求发送到web服务器,web服务器在收到请求后,将请求携带的参数写入到缓冲区stdin,然后php写入预定义变量的时候,会从stdin中取出这些参数然后装入到对应的预定义变量中$_GET,$_POST,$_REQUEST中

自己跟踪代码看整个php的流程

1>执行main/mian.c中的php_module_startup函数
2>执行php_startup_auto_globals函数,该函数在php_variables.c中定义的

//php_variable.c
void php_startup_auto_globals(void)
{
    zend_register_auto_global(zend_string_init("_GET", sizeof("_GET")-1, 1), 0, php_auto_globals_create_get);
    zend_register_auto_global(zend_string_init("_POST", sizeof("_POST")-1, 1), 0, php_auto_globals_create_post);
    zend_register_auto_global(zend_string_init("_COOKIE", sizeof("_COOKIE")-1, 1), 0, php_auto_globals_create_cookie);
    zend_register_auto_global(zend_string_init("_SERVER", sizeof("_SERVER")-1, 1), PG(auto_globals_jit), php_auto_globals_create_server);
    zend_register_auto_global(zend_string_init("_ENV", sizeof("_ENV")-1, 1), PG(auto_globals_jit), php_auto_globals_create_env);
    zend_register_auto_global(zend_string_init("_REQUEST", sizeof("_REQUEST")-1, 1), PG(auto_globals_jit), php_auto_globals_create_request);
    zend_register_auto_global(zend_string_init("_FILES", sizeof("_FILES")-1, 1), 0, php_auto_globals_create_files);
}

//zend_compile.c,将各个预定义变量写入
int zend_register_auto_global(zend_string *name, zend_bool jit, zend_auto_global_callback auto_global_callback){
    zend_auto_global auto_global;
    int retval;
    auto_global.name = zend_new_interned_string(name);
    auto_global.auto_global_callback = auto_global_callback;
    auto_global.jit = jit;

    retval = zend_hash_add_mem(CG(auto_globals), auto_global.name, &auto_global, sizeof(zend_auto_global)) != NULL ? SUCCESS : FAILURE;

    zend_string_release(name);
    return retval;
}

//zend_compile.c,将各个变量的key-value写入到hashtable中
static zend_always_inline void *zend_hash_add_mem(HashTable *ht, zend_string *key, void *pData, size_t size){
    zval tmp, *zv;

    ZVAL_PTR(&tmp, NULL);
    if ((zv = zend_hash_add(ht, key, &tmp))) {    //这一步的$_REQUEST可能被攻击
        Z_PTR_P(zv) = pemalloc(size, ht->u.flags & HASH_FLAG_PERSISTENT);
        memcpy(Z_PTR_P(zv), pData, size);
        return Z_PTR_P(zv);
    }
    return NULL;
}

laruence的博客:

要知道PHP是怎么处理的,首先我们要了解,$_GET, $_POST, $_COOKIE等变量的构造过程。
每个请求到来以后,apache处理到response阶段的时候, 会将控制权交给PHP模块, PHP模块会在处理请求之前首先间接调用 php_request_startup函数,在php_request_startup中:

#ifndef APACHE_HOOKS
int php_request_startup(void)
{
    int retval = SUCCESS;

#ifdef HAVE_DTRACE
    DTRACE_REQUEST_STARTUP(SAFE_FILENAME(SG(request_info).path_translated), SAFE_FILENAME(SG(request_info).request_uri), (char *)SAFE_FILENAME(SG(request_info).request_method));
#endif /* HAVE_DTRACE */

#ifdef PHP_WIN32
# if defined(ZTS)
    _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
# endif
    PG(com_initialized) = 0;
#endif

#if PHP_SIGCHILD
    signal(SIGCHLD, sigchld_handler);
#endif

    zend_try {
        PG(in_error_log) = 0;
        PG(during_request_startup) = 1;

        php_output_activate();

        /* initialize global variables */
        PG(modules_activated) = 0;
        PG(header_is_being_sent) = 0;
        PG(connection_status) = PHP_CONNECTION_NORMAL;
        PG(in_user_include) = 0;

        zend_activate();
        sapi_activate();

#ifdef ZEND_SIGNALS
        zend_signal_activate();
#endif

        if (PG(max_input_time) == -1) {
            zend_set_timeout(EG(timeout_seconds), 1);
        } else {
            zend_set_timeout(PG(max_input_time), 1);
        }

        /* Disable realpath cache if an open_basedir is set */
        if (PG(open_basedir) && *PG(open_basedir)) {
            CWDG(realpath_cache_size_limit) = 0;
        }

        if (PG(expose_php)) {
            sapi_add_header(SAPI_PHP_VERSION_HEADER, sizeof(SAPI_PHP_VERSION_HEADER)-1, 1);
        }

        if (PG(output_handler) && PG(output_handler)[0]) {
            zval oh;

            ZVAL_STRING(&oh, PG(output_handler));
            php_output_start_user(&oh, 0, PHP_OUTPUT_HANDLER_STDFLAGS);
            zval_ptr_dtor(&oh);
        } else if (PG(output_buffering)) {
            php_output_start_user(NULL, PG(output_buffering) > 1 ? PG(output_buffering) : 0, PHP_OUTPUT_HANDLER_STDFLAGS);
        } else if (PG(implicit_flush)) {
            php_output_set_implicit_flush(1);
        }

        /* We turn this off in php_execute_script() */
        /* PG(during_request_startup) = 0; */

        php_hash_environment();
        zend_activate_modules();
        PG(modules_activated)=1;
    } zend_catch {
        retval = FAILURE;
    } zend_end_try();

    SG(sapi_started) = 1;

    return retval;
}

其中的zend_variables.c文件中php_hash_environment函数:

PHPAPI int php_hash_environment(void)
{
    memset(PG(http_globals), 0, sizeof(PG(http_globals)));
    zend_activate_auto_globals();
    if (PG(register_argc_argv)) {
        php_build_argv(SG(request_info).query_string, &PG(http_globals)[TRACK_VARS_SERVER]);
    }
    return SUCCESS;
}

//回调zend_variables.c中的zend_activate_auto_globals函数将请求的value写入到对应的$_POST和$_GET预定义变量中
ZEND_API void zend_activate_auto_globals(void) /* {{{ */
{
    zend_auto_global *auto_global;

    ZEND_HASH_FOREACH_PTR(CG(auto_globals), auto_global) {
        if (auto_global->jit) {
            auto_global->armed = 1;
        } else if (auto_global->auto_global_callback) {
            //这里会回调php_auto_globals_create_post,php_auto_globals_create_get,php_auto_globals_create_request函数处理对应的变量
            auto_global->armed = auto_global->auto_global_callback(auto_global->name);
        } else {
            auto_global->armed = 0;
        }
    } ZEND_HASH_FOREACH_END();
}

拿其中一个$_REQUEST数据来看,php_variables.c文件中php_auto_globals_create_request函数对应代码:

static zend_bool php_auto_globals_create_post(zend_string *name)
{
    if (PG(variables_order) &&
            (strchr(PG(variables_order),'P') || strchr(PG(variables_order),'p')) &&
        !SG(headers_sent) &&
        SG(request_info).request_method &&
        !strcasecmp(SG(request_info).request_method, "POST")) {
        //写入数据
        sapi_module.treat_data(PARSE_POST, NULL, NULL);
    } else {
        zval_ptr_dtor(&PG(http_globals)[TRACK_VARS_POST]);
        array_init(&PG(http_globals)[TRACK_VARS_POST]);
    }

    zend_hash_update(&EG(symbol_table), name, &PG(http_globals)[TRACK_VARS_POST]);
    Z_ADDREF(PG(http_globals)[TRACK_VARS_POST]);

    return 0; /* don't rearm */
}

可以看到,离成功不远了,sapi_module.treat_data 也就是php_default_treat_data,
在php_default_treat_data中,对于变量,都调用php_register_variable_safe来注册变量,
而php_register_variable_safe最终会调用php_register_variable_ex:

zend_variables.c文件中zend_register_auto_global和zend_activate_auto_globals之间的关系,应该有先后顺序的问题。
其中zend_register_auto_global看似是相当于注册声明对应的预定义变量名,而zend_activate_auto_globals才是真正的将值写入到$_GET和$_POST变量中的操作。
提交最大变量数限制,php_varialbles.c中add_post_vars做限制,SAPI_POST_HANDLER_FUNC(php_std_post_handler)

参考资料:
http://www.laruence.com/2008/...
http://www.laruence.com/2008/...
http://www.php-internals.com/...


hizengzeng
177 声望10 粉丝

hizengzeng