0x00 前言
前幾天做到了一些無參數RCE的題目,自己記性不行,這里總結一下,方便以后隨時撿起來看看.
0x01 源碼特點分析
- 源碼中過濾了常用偽協議,用正則表達式規定只可以使用無參數函數進行RCE,具體可以看下段代碼:
第一個if過濾掉了常用的偽協議,無法直接使用偽協議讀取文件.
第二個if用了一個比較復雜的正則表達式,對輸入的exp參數進行匹配,把匹配到的字符串全部替換為NULL(不改變exp的值),然后檢測替換之后的字符串是否只剩一個分號.
來分析一下那個奇怪的正則表達式,[a-z,_]限制了只可以是小寫字母以及下划線,(?R)是引用當前表達式的意思,就是把當前的整個表達式替換到(?R)的位置,(?R)?這里最后一個?表示這個引用可有可無,就是說,可以匹配,也可以不匹配.也就是說這個正則只可以匹配到無參數的函數,如a(),a(b()),a(b(c())).
第三個if過濾了一些函數的關鍵字,需要構造別的方法去繞過.
-
無參數RCE,具體怎么解釋呢?
var_dump(scandir()); //可以使用
var_dump(scandir('.')) //不可以使用
也就是說,我們可以使用函數套娃的形式,來逐層實現我們RCE的命令.
0x02 以一道題目來看無參數RCE
[GXYCTF2019]禁止套娃
題目存在git泄露,這里有一個問題就是用dirsearch掃會導致訪問過快從而掃不到./git,不知道是掃描工具原因還是題目問題.
- 來看源碼
傳入exp參數,經過三個if函數判斷后,如果滿足條件會執行exp參數.
eval($_GET['exp']); 典型的無參數RCE.
-
這中過濾條件,想要getshell沒戲了,從源碼中得知flag應該就在flag.php中,那么這道題目的意思就是使用無參數RCE來讀取到flag.php中的內容.
-
具體改怎么做呢?下面看幾個函數:
scandir():掃描當前目錄下的文件,並以數組的形式返回
localeconv():返回一個包含本地數字及貨幣格式信息的數組,該數組的第一項就是'.'
current():返回當前數組的當前單元,默認值是第一個
pos():同current()
組合拳1:獲取當前目錄下的文件
要獲取當前目錄下的文件,應該想到scandir('.'),知道上面的三個函數,就可以把它構造出來了.
payload:var_dump(scandir(current(localeconv())); //別忘了加分號
用current(localeconv())代替了'.'
- 要讀取的flag.php在數組中的索引值是3,也就是在第4個位置,問題就是該怎么讀取到呢?好在php為我們提供了這些函數:
array_reverse():以相反的順序返回數組
next():將數組中的內部指着指向下一個元素並輸出
array_rand():從數組中隨機獲取一個或多個單元
組合拳2:讀取到flag.php的內容
payload1:readfile(next(array_reverse(scandir(current(localeconv())))));
因為flag.php是在數組中的倒數第二個位置,所以將數組倒置之后變成了第二個位置,用next()函數將數組內部指針位置指向第二個元素,成功讀取flag.php
payload2:readfile(array_rand(scandir(current(localeconv()))));
多刷新幾次,即可讀取到flag.php
5.另一種解法:
session_id():可以獲取到當前的會話ID.
session_start():會創建新會話或者重用現有會話。如果通過 GET 或者 POST 方式,或者使用 cookie 提交了會話 ID, 則會重用現有會話。
因為SessionID是存放在客戶端的cookie中的,所以我們可以手動設置會話ID為flag.php來讀取到其中的內容
payload:readfile(session_id(session_start())); //注意一點,session_start()必須在session_id()之前調用,使服務器端使用我們cookie提交的會話ID.
0x03 后話
可以使用函數還有很多,以后再另寫一篇文章總結吧.