RCE篇之無參數rce


轉載自:https://arsenetang.github.io/2021/07/26/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 文件中語法高亮版本的代碼


免責聲明!

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



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