Hed9eh0g

前进的路上总是孤独的

php复杂变量解析学习记录

本文共计有2293个字

前言

对于php的复杂变量解析规则一直有些模糊,最近大致弄懂了一些规律,在此记录相关知识点。

知识基础

单双引号的区别

首先要知道php的双引号和单引号作用的一个基本的不同之处。
举一个例子:

<?php
$test='hello world!';
echo "$test";
echo '$test';

输出结果显然是:

hello world!
$test

也就是说在php中如果出现单引号,就直接将单引号里面的内容作为文本。
如果出现双引号,那么php首先会去看看双引号里面有没有变量,如果此时有变量被双引号包裹,那么php会先进行变量解析,所谓解析就是将变量替换成它的值。

变量的识别

那么php又是如何识别到双引号里面的变量的?
php规定:
任何具有string表达的标量变量,数组单元或对象属性都可使用此语法。只需简单地像在string以外的地方那样写出表达式,然后用花括号 {} 把它括起来即可。由于 { 无法被转义,只有 $ 紧挨着 { 时才会被识别。可以用 {\$ 来表达 {$
举一个例子容易了解花括号的作用:

<?php
$test="hello world!";
echo "${test}";
echo "<br>";
echo "{$test}";

输出的结果是:

hello world!
hello world!

另外一个例子:

<?php
$fruit="apple";
echo "there are many {$fruit}s";

输出结果:

there are many apples

结合两个例子可以知道,花括号起到标记变量的界限的作用,在第二个例子中如果没有花括号那么php将会把变量识别为$fruits进而使结果异常。
另外也说明php会尽量多地取组合可用的字符作为变量名。
按理说文章可就此结束,可是这东西却和系统函数及自定义的方法挂上了关系,从而引出了后文。

变量解析+系统函数、方法

仍然来源于官方文档:
函数、方法、静态类变量和类常量只有在 PHP 5 以后才可在 {$} 中使用。然而,只有在该字符串被定义的命名空间中才可以将其值作为变量名来访问。只单一使用花括号 ({}) 无法处理从函数或方法的返回值或者类常量以及类静态变量的值。
不必解释,直接上例子:

<?php
$Hed9eh0g="test";
function getname(){
    echo "Hed9eh0g";
    echo "<br>";
    return "Hed9eh0g";
}
echo "${getname()}";

输出结果:

Hed9eh0g
test

可以看出在双引号包裹的${}这种形式内部如果有方法名,则这个方法是可以执行的,本例子中首先执行了echo语句,然后再return对应的值Hed9eh0g,与外层形成新的结果${Hed9eh0g},此时php会将其识别为一个变量,然后进行解析并替换成其对应的结果。
如果再在getname()外层包裹一层${},那么结果将会报错,因为根据刚才的推理可知php最终会识别$test为一个变量,而这是一个我们没有定义过的变量:
《php复杂变量解析学习记录》
虽然会报错,但是不影响getname函数的echo语句的执行。

实验

<?php
highlight_file(__FILE__);

$str = @(string)$_GET['str'];
eval('$str = "'.addslashes($str).'";');
?>

分别传入两个payload:

?str={${phpinfo()}}
?str=${$phpinfo()}}

结果:
payload1:
《php复杂变量解析学习记录》
payload2,与payload1一样可以执行函数,只是多出最后两行:
《php复杂变量解析学习记录》
解释结果:
eval函数将传入的参数当作php代码执行,所以执行代码就是:

$str = “{${phpinfo()}}”;

由双引号包裹,存在变量的解析,{}作为变量的边界,这里面最内层有函数,所以首先会执行phpinfo()
而函数phpinfo(),会返回 true,因为 true,是bool类型的变量,然后进行类型转化,转化为字符串1,所以调用的参数就是$1,这也是上面第二个payload报错为Undefined variable: 1的原因。
下面我们做第二个实验,将addslashes 用单引号包裹,结果会是什么样的呢?代码如下:

<?php
highlight_file(__FILE__);

$str = @(string)$_GET['str'];
eval("$str = '".addslashes($str)."';");
?>

仍然用刚才的两个payload,结果:
payload1:
《php复杂变量解析学习记录》
payload2:
《php复杂变量解析学习记录》
第一个失败了,第二个却成功了,这是为什么呢?
复杂变量解析的前提是双引号包裹,第二次实验中,addslashes函数部分被单引号包裹,所以只是简单的字符串,但是前面"$str"却是被双引号包裹,所以可以进行复杂变量解析。
那么payload1的"{${phpinfo()}}"和payload2的"${${phpinfo()}}"看样子都可以进行解析之后执行函数?但事实上却只有payload2可以实现。问题出在哪?
这里注意了,$str此时是左值,也即位于赋值语句的左侧,而左值必须得是一个变量,也即必须由字符$开头,显然payload1的开头字符是{,因此它压根不是一个变量,此时前面我们所谈的变量解析的特性都没有用。

参考文章

1、https://panda1g1.github.io/2019/04/19/php%20%E5%A4%8D%E6%9D%82%E5%8F%98%E9%87%8F%E8%A7%A3%E6%9E%90/
2、https://www.anquanke.com/post/id/176331

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注