前置知识
可变变量
这是PHP的一个特性,允许一个变量中的内容作为变量名使用,作用上类似于其他的指针。
代码示例
$a = "Cat";
$b = "a";
$$b = "Dog";
echo $a;
运行结果:Dog
extract()
函数
extract()
函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
该函数返回成功设置的变量数目。
用PHP代码进行实现的话:
foreach($my_array as $x => $y)
{
$$x = $y;
}
参考: 菜鸟教程
代码示例
$a = "Original";
$my_array = array("a" => "Cat","b" => "Dog", "c" => "Horse");
extract($my_array);
echo "\$a = $a; \$b = $b; \$c = $c";
运行结果:$a = Cat; $b = Dog; $c = Horse
场景
选取自BJDCTF2020-Mark loves cat
<?php
$flag = file_get_contents('/flag');
$yds = "dog";
$is = "cat";
$handsome = 'yds';
extract($_POST);
foreach($_GET as $x => $y)
{
$$x = $$y;
}
foreach($_GET as $x => $y)
{
if($_GET['flag'] === $x && $x !== 'flag')
{
exit($handsome);
}
}
if(!isset($_GET['flag']) && !isset($_POST['flag']))
{
exit($yds);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag')
{
exit($is);
}
echo "the flag is: ".$flag;
?>
解法一:绕过所有if
分析
我们阅读代码,发现最后输出了变量flag中的内容,要到达输出变量flag的语句,则需要绕过所有if。
观察每个if的条件,发现我们需要:
- GET传参中flag的值不能与其他键名称相同
- GET传参与POST传参中必须有一个flag键
GET传参与POST传参中flag键的的值不能为flag
这条件看起来很宽松啊,好像随便传个?flag=1
就能拿到flag。但真的是这样吗?别忘记前面还有一段代码:extract($_POST); foreach($_GET as $x => $y) { $$x = $$y; }
这段代码对变量进行了覆盖,如果我们在POST传参里面传递了
flag=1
,那么就会执行:$flag = '1'
。
如果在GET传参里面传递了flag=1
,那么就会执行:$flag = $1
,而$1
为空,所以$flag
会被置空。
这可有点棘手了,POST传递flag
的话$flag
都会被覆盖,GET传递flag=flag
的话,违反了第三个if的条件。
那么我通过GET传递flag=a&a=flag
,在处理GET参数时不就相当于执行了$flag = ${$a}
,也就是$flag = $flag
,这样就不会影响到$flag
中的值了,但是第一个if限制了我们这样操作。
似乎现有的思路都被堵住了,这该怎么办呢?
破局的方法:强比较
我们可以利用PHP强比较会进行类型比较的性质来绕过第一个if。
在解析GET与POST的参数中的键时,如果键为数字,那么PHP会将其识别为整型而不是字符串,而数字作为参数的值时,会将其识别为字符串。
利用这个特性就可以绕过第一个if。
Payload: ?1=flag&flag=1
注意这里的顺序不能乱,必须要先执行1=flag,然后执行flag=1,这样才不会导致flag被空变量1覆盖。
成功拿到flag。
解法二(最简):覆盖变量yds
通过将$yds
覆盖为$flag
的值,在执行第二个if的同时将$flag
的值带出来,那么首先要做到的就是改变$yds
的值为$flag
的值。
那么只能通过GET传参进行变量覆盖。
Payload: ?yds=flag
这条Payload使得程序在对GET参数进行处理时将$yds
覆盖为$flag
的值,同时这条Payload能不触发第一条if,但是会触发第二条if,从而将$yds
中的内容进行输出,成功拿到flag。
其他解法
当然也存在其他解法,也就是通过覆盖$handsome
与$is
,但是这些方法就留给读者们去自行尝试。