1. 程式人生 > 其它 >RCE篇之無引數rce

RCE篇之無引數rce

無引數rce

無參rce,就是說在無法傳入引數的情況下,僅僅依靠傳入沒有引數的函式套娃就可以達到命令執行的效果,這在ctf中也算是一個比較常見的考點,接下來就來詳細總結總結它的利用姿勢

核心程式碼

if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);
}

這段程式碼的核心就是隻允許函式而不允許函式中的引數,就是說傳進去的值是一個字串接一個(),那麼這個字串就會被替換為空,如果替換後只剩下;,那麼這段程式碼就會被eval執行。而且因為這個正則表示式是遞迴呼叫的,所以說像a(b(c()));

第一次匹配後就還剩

a(b());,第二次匹配後就還剩a();,第三次匹配後就還剩;了,所以說這一串a(b(c())),就會被eval執行,但相反,像a(b('111'));這種存在引數的就不行,因為無論正則匹配多少次它的引數總是存在的。那假如遇到這種情況,我們就只能使用沒有引數的php函式,

下面就來具體介紹一下:

1、getallheaders()

這個函式的作用是獲取http所有的頭部資訊,也就是headers,然後我們可以用var_dump把它打印出來,但這個有個限制條件就是必須在apache的環境下可以使用,其它環境都是用不了的,我們到burp中去做演示,測試程式碼如下:

<?php
highlight_file(__FILE__); if(isset($_GET['code'])){ if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) { eval($_GET['code']);} else die('nonono');} else echo('please input code'); ?>

可以看到,所有的頭部資訊都已經作為了一個陣列列印了出來,在實際的運用中,我們肯定不需要這麼多條,不然它到底執行哪一條呢?所以我們需要選擇一條出來然後就執行它,這裡就需要用到php

中操縱陣列的函數了,這裡常見的是利用end()函式取出最後一位,這裡的效果如下圖所示,而且它只會以字串的形式取出而不會取出鍵,所以說鍵名隨便取就行:

那我們把最前面的var_dump改成eval,不就可以執行phpinfo了嗎,換言之,就可以實現任意php程式碼的程式碼執行了,那在沒有過濾的情況下執行命令也就輕而易舉了,具體效果如下圖所示:

2、get_defined_vars()

上面說到了,getallheaders()是有侷限性的,因為如果中介軟體不是apache的話,它就用不了了,那我們就介紹一種更為普遍的方法get_defined_vars(),這種方法其實和上面那種方法原理是差不多的:

可以看到,它並不是獲取的headers,而是獲取的四個全域性變數$_GET $_POST $_FILES $_COOKIE,而它的返回值是一個二維陣列,我們利用GET方式傳入的引數在第一個陣列中。這裡我們就需要先將二維陣列轉換為一維陣列,這裡我們用到current()函式,這個函式的作用是返回陣列中的當前單元,而它的預設是第一個單元,也就是我們GET方式傳入的引數,我們可以看看實際效果:

這裡可以看到成功輸出了我們二維陣列中的第一個資料,也就是將GET的資料全部輸出了出來,相當於它就已經變成了一個一維陣列了,那按照我們上面的方法,我們就可以利用end()函式以字串的形式取出最後的值,然後直接eval執行就行了,這裡和上面就是一樣的了:

總結一下,這種方法和第一種方法幾乎是一樣的,就多了一步,就是利用current()函式將二維陣列轉換為一維陣列,如果大家還是不瞭解current()函式的用法,可以接著往下看文章,會具體介紹的哦

這裡還有一個專門針對$_FILES下手的方法,可以參考這篇文章:https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/

3、session_id()

這種方法和前面的也差不太多,這種方法簡單來說就是把惡意程式碼寫到COOKIEPHPSESSID中,然後利用session_id()這個函式去讀取它,返回一個字串,然後我們就可以用eval去直接執行了,這裡有一點要注意的就是session_id()要開啟session才能用,所以說要先session_start(),這裡我們先試著把PHPSESSID的值取出來:

直接出來就是字串,那就非常完美,我們就不用去做任何的轉換了,但這裡要注意的是,PHPSESSIID中只能有A-Z a-z 0-9-,所以說我們要先將惡意程式碼16進位制編碼以後再插入進去,而在php中,將16進位制轉換為字串的函式為hex2bin

那我們就可以開始構造了,首先把PHPSESSID的值替換成這個,然後在前面把var_dump換成eval就可以成功執行了,如圖:

成功出現phpinfo,穩穩當當,這種方法我認為是最好的一種方法,很容易理解,只是記得要將惡意程式碼先16進位制編碼一下哦

4.php函式直接讀取檔案

上面我們一直在想辦法在進行rce,但有的情況下確實無法進行rce時,我們就要想辦法直接利用php函式完成對目錄以及檔案的操作, 接下來我們就來介紹這些函式:

1.localeconv

官方解釋:localeconv() 函式返回一個包含本地數字及貨幣格式資訊的陣列。

這個函式其實之前我一直搞不懂它是幹什麼的,為什麼在這裡有用,但實踐出真知,我們在測試程式碼中將localeconv()的返回結果輸出出來,這裡很神奇的事就發生了,它返回的是一個二維陣列,而它的第一位居然是一個點.,那按照我們上面講的,是可以利用current()函式將這個點取出來的,但這個點有什麼用呢?點代表的是當前目錄!那就很好理解了,我們可以利用這個點完成遍歷目錄的操作!相當於就是linux中的ls,具體請看下圖:

2.scandir

這個函式很好理解,就是列出目錄中的檔案和目錄

3.current(pos)

這裡首先宣告,pos()函式是current()函式的別名,他們倆是完全一樣的哈

這個函式我們前面已經用的很多了,它的作用就是輸出陣列中當前元素的值,只輸出值而忽略掉鍵,預設是陣列中的第一個值,如果要移動可以用下列方法進行移動:

4.chdir()

這個函式是用來跳目錄的,有時想讀的檔案不在當前目錄下就用這個來切換,因為scandir()會將這個目錄下的檔案和目錄都列出來,那麼利用運算元組的函式將內部指標移到我們想要的目錄上然後直接用chdir切就好了,如果要向上跳就要構造chdir('..')

5.array_reverse()

將整個陣列倒過來,有的時候當我們想讀的檔案比較靠後時,就可以用這個函式把它倒過來,就可以少用幾個next()

6.highlight_file()

列印輸出或者返回 filename 檔案中語法高亮版本的程式碼,相當於就是用來讀取檔案的

例題解析——–GXYCTF 2019禁止套娃

這道題首先是一個git原始碼洩露,我們先用GitHack把原始碼跑下來,內容如下:

<?php
include "flag.php";
echo "flag在哪裡呢?<br>";
if(isset($_GET['exp'])){
    if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
        if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
            if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
                // echo $_GET['exp'];
                @eval($_GET['exp']);
            }
            else{
                die("還差一點哦!");
            }
        }
        else{
            die("再好好想想!");
        }
    }
    else{
        die("還想讀flag,臭弟弟!");
    }
}
// highlight_file(__FILE__);
?>

可以看出它是一個有過濾的無參rce,由於它過濾掉了et,導致我們前兩種的方法都用不了,而且它也過濾了hex bin,第三種方法也不能像我們上面講的一樣先16進位制編碼了,而且我抓包以後都看不到PHPSESSID的引數,估計第三種方法也用不了,但有了前面的鋪墊,用第四種方法就可以很簡單的解決了,首先遍歷當前目錄:

可以看到flag.php是倒數第二個,那我們就把它反轉一下,然後再用一個next()就是flag.php這個檔案了

勝利就在眼前,直接highlight_file讀取這個檔案就拿到flag了:

思路總結

scandir(current(localeconv()))是檢視當前目錄
加上array_reverse()是將陣列反轉,即Array([0]=>index.php[1]=>flag.php=>[2].git[3]=>..[4]=>.)
再加上next()表示內部指標指向陣列的下一個元素,並輸出,即指向flag.php
highlight_file()列印輸出或者返回 filename 檔案中語法高亮版本的程式碼