作者 by Yichen / 2024-02-21 / 暂无评论 / 42 个足迹
PHP编译非顶级作用域函数时,原始函数名和生成的key将会顺序储存在 DECLARE_FUNCTION这个opline的属性中,在执行DECLARE_FUNCTION这个opcode时,才会将真正的原始函数名放进函数表中。
也就是说,作用域如果不是顶级的函数,在编译阶段会先以一个\0开头的函数名被放入函数表中,在执行阶段于DECLARE_FUNCTION的处理器中才会将真正的函数名放入函数表。
static void zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_ast_decl *decl, zend_bool toplevel) /* {{{ */
{
...
zend_register_seen_symbol(lcname, ZEND_SYMBOL_FUNCTION);
if (toplevel) {
if (UNEXPECTED(zend_hash_add_ptr(CG(function_table), lcname, op_array) == NULL)) {
do_bind_function_error(lcname, op_array, 1);
}
zend_string_release_ex(lcname, 0);
return;
}
/* Generate RTD keys until we find one that isn't in use yet. */
key = NULL;
do {
zend_tmp_string_release(key);
key = zend_build_runtime_definition_key(lcname, decl->start_lineno);
} while (!zend_hash_add_ptr(CG(function_table), key, op_array));
...
}当toplevel为true的时候,进入到第一个if语句逻辑,就是直接将当前函数名lcname加入函数表;当toplevel为false的时候,则进入到下面的do while循环,使用zend_build_runtime_definition_key函数生成一个key,将key作为函数名加入函数表。
再来看这个key的生成过程
可见,这个函数的核心是一个字符串格式化,最后的key是按照如下算法生成:
'\0' + name + filename + ':' + start_lineno + '$' + rtd_key_counter除了第一个0字符,后面四部分的含义如下:
rtd_key_counter 一个全局访问计数,每次执行会自增1,从0开始(文件被访问了多少次)
<?php
error_reporting(0);
show_source(__FILE__);
$count = (file_exists('count.db')) ? (int)file_get_contents('count.db') + 1 : 1;<br />file_put_contents('count.db', $count);
echo "你是第{$count}个访问此页面的人!";
if(False){
function neuq() {
echo '这是你的flag:';
show_source("flag.php");
}
}
$csa = $_POST['csa'];
$csa();
?>
可以看到我们可以直接调用到一个函数,但是因为if(false),neuq(),这个函数不能被直接调用到,但在php的编译过程中它被用key来代替记录了,假设if(false)被执行,就会将neuq添加到函数表,此时记录它的key就是这个临时函数的名字
因此先访问phpinfo,拿到文件路径,再构造临时函数名:
\%00neuq/var/www/html/index.php:10$1
独特见解