Complex syntax of PHP variable parsing

FOREWORD

本篇是一篇小记;php是一门很灵活的语言,也有一些复杂变量语法;今天也偶然翻到了队长的博客看到了相关的内容,也就复习下复杂变量;

text

先来看一个例子;

1
2
3
4
5
6
7
8
9
<?php
highlight_file(__FILE__);
$str = $_GET['str'];
$str = addslashes($str);
if(preg_match('/[A-Za-z0-9]+\(/i',$str) == 1){
die('hack');
}
eval('$a="' . $str . '";');
?>

Topic explanation

分析题目并不是很难,逻辑也是很好理解,接收GET传参,然后对内容进行addslashes函数处理,将一些敏感字符前面加上转义字符;然后进行正则,过去正则之后才是进行eval执行;

先来分步看下,如果没有经过addslashes函数处理,也没有经过正则处理;那么我们的思路必然是进行闭合,然后进行命令执行;

然后如果我们加上了addslashes函数进行处理之后,也就无法进行闭合,那么就需要用到我们需要说的复杂变量;

Introduction of complex variables

关于复杂变量的理解,我们先来看一个例子;

1
2
3
4
5
<?php
$s1mple = "s1mple";

echo "{$s1mple}";
echo "${s1mple}";

感兴趣的师傅可以本地复现下,会发现两者的回显效果是一样的;

php规定:
任何具有string表达的标量变量,数组单元或对象属性都可使用此语法。只需简单地像在string以外的地方那样写出表达式,然后用花括号 {} 把它括起来即可。由于 { 无法被转义,只有 $ 紧挨着 { 时才会被识别。可以用 {$ 来表达 **{$**。并且这里有个小细节,在php中,单引号和双引号表达的效果有时候是不一样的,双引号可以对其中的变量进行解析,而单引号只是将其中的字符全部当作字符串从而输出;比如如下的一个小例子;

1
2
3
4
5
6
<?php
$s1mple = "s1mple";

echo '$s1mple';
echo "<br>";
echo "$s1mple";

感兴趣的师傅测试会发现结果不一样,这也就是单引号和双引号的不同之处;所以一般涉及复杂变量利用的都是用双引号来闭合的,有些题中师傅们可以将其作为一个参考点;

Complex variables combined with functions

这个用法也是来源于php的文档,函数,方法,静态变量和类常量只有在php5以后猜可以在{$}中使用;然而,只有在该字符串被定义的命名空间中才可以将其值作为变量名来访问,只单一的使用花括号无法处理从函数或方法的返回值或者类常量以及类静态变量的值;

比如如下的例子:

1
2
3
<?php
$s1mple = "${phpinfo()}";
?>

DMAJDe.png

可以看到我们的phpinfo函数已经执行,那么按照我们之前的说法,也是可以进行${${phpinfo()}}这样的写法的;一样可以成功解析;那么为什么这种写法可以成功解析呢?其原因还是在于在php中,php是可以接收函数的返回值作为变量名的。我们phpinfo被成功的解析,然后将返回值做为了变量名;

这里通过例子解释的更明白些,看一个例子;

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);

$root = 's1mple';

${system('whoami')} = 's1mple_is_here';

echo $root;


?>

运行之后观察其回显效果;

DMAXP1.png

这里我们不难看到,php引擎先是处理了函数,进行执行之后返回了root;然后将root作为变量名重新赋值,所以最后才是覆盖了之前的$root的变量;

回到题目

解释了php复杂变量的利用原理,那么回到题目,如果我们加上了addslashes函数处理;我们无法进行引号闭合,那就只能进行复杂变量的利用了;

DMAUUA.png

其原理之前也是讲述过,先来执行命令,然后将回显的效果当作变量名;所以我们这个命令的回显结果就是我们的变量名;但是在这之前我们的命令已经执行完毕并进行回显;

如果我们再加入最后一个限制的条件的话;就是加入我们的正则表达式;

1
2
3
4
5
6
7
8
9
<?php
highlight_file(__FILE__);
$str = $_GET['str'];
$str = addslashes($str);
if(preg_match('/[A-Za-z0-9]+\(/i',$str) == 1){
die('hack');
}
eval('$a="' . $str . '";');
?>

加入我们的正则之后,如果我们再使用之前的payload的话,会发现已经被ban了;返回的是hack;因为我们正则的过滤,是不允许括号前出现数字和字符的,我们 ium按照payload2的方法来,显然是触犯了这个规则;

那么解决最后的问题方法,就是需要利用php的一个特性了;比如我们传入${(phpinfo)()}如此我们就避免来再括号前面出现字母和数字的情形;

DMA3jO.png

DMAYHH.png

简单来说就是我们system(‘ls’);和“system”(‘ls’);和(system)(ls);是等价的;还和”\x\x\x”(‘id’);其中(system)()这样的特性只有在php7的情况下才可以使用;