講到 PHP 的 include
,逐家加減攏會想著 LFI (Local File Inclusion) 這个老朋友。本來想講,這个古早味的漏縫欲耍 LFI to RCE 到 Solving "includer's revenge" from hxp ctf 2021 without controlling any files 彼改應該已經變無步矣,毋過咱「規世界上好的程式語言」PHP 原仔是會使變新齣頭予咱!這篇文章欲來紹介 DeadsecCTF2025 的一題「baby-web」,雖然無用 PHP filter chain 烏白隨咱生資料彼改遐爾仔欻,毋過總是算一个好耍的特性。
這題的 code 嘛算簡單,一隻 upload.php
處理檔案上傳,閣一隻 index.php
去共 include,
<?php
// index.php
$include_url = basename($_GET['url']);
$SANDBOX = ...;
// ... 一寡 sandbox 檢查/建立的 code ...
if (!file_exists($SANDBOX . '/' . $include_url)) die("Nope :<");
if (!preg_match("/\.(zip|bz2|gz|xz|7z)/i", $include_url)) die("Nope :<");
@include($SANDBOX . '/' . $include_url);
?>
<?php
// upload.php
$allowed_extensions = ['zip', 'bz2', 'gz', 'xz', '7z'];
$allowed_mime_types = [
'application/zip', 'application/x-bzip2', 'application/gzip',
'application/x-gzip', 'application/x-xz', 'application/x-7z-compressed',
];
function filter($tempfile) {
$data = file_get_contents($tempfile);
if (stripos($data, "__HALT_COMPILER();") !== false ||
stripos($data, "PK") !== false ||
stripos($data, "<?") !== false ||
stripos(strtolower($data), "<?php") !== false) {
return true;
}
return false;
}
// ... (後壁的上傳理路)
簡單講這个挑戰有 3 个重點:
zip
, bz2
, gz
, xz
, 7z
這幾種壓縮檔去裡。__HALT_COMPILER();
、PK
、<?
抑是 <?php
這款字,若有就失敗。index.php
會用 basename($_GET['url'])
來提著檔名,確保你袂使利用 ../
來跳目錄,嘛袂使直接用 phar://
這款的 wrapper。最後,伊會共你傳去裡的檔案 include
入來。include
一个壓縮檔是會使創啥?內容都無 <?
矣閣,是欲按怎走 PHP code?這就是這題上趣味的所在。
欲了解這个問題咱著愛共 PHP 的ia̋n-jín蓋掀開,看伊底層的 C source code 是按怎運作的。問題的關鍵,就佇 include
這个動作頂頭。
若 PHP 欲用 include
去共一个檔案印入來的時,伊會去叫一个叫做 compile_filename
的函式。這个函式會沓沓仔一步一步行,上尾仔才閣去叫著 phar_compile_file
來處理咱的 phar 檔案。
關鍵的 code 佇 phar_compile_file
內底遮:
if (strstr(ZSTR_VAL(file_handle->filename), ".phar") && !strstr(ZSTR_VAL(file_handle->filename), "://")) {
// ...
}
遮的 code 檢查傳入來的檔名敢有包含 .phar
這个字串。若有,伊就煞會共這个檔案當做是 phar archive 來處理,就算伊無用 phar://
wrapper 來引入。
閣較趣味的代誌發生佇後面的 phar_open_from_fp
這个函式。 這个函式的流程大概是按呢:
\x1f\x8b\x08
)。若是,就共伊自動解壓縮,然後重新開始處理解壓縮了後的檔案。BZh
)。若是,仝款共伊自動解壓縮。__HALT_COMPILER();
這个 phar 檔案的標記。結論就是:若你 include
一个檔名/路徑有 .phar
的檔案(親像 my_file.phar.gz
),PHP 的底層會自動判斷伊是 Gzip 壓縮檔,共伊解壓縮了後,才閣當做 phar 檔案來執行!
這个發現直接就共頭前的所有限制破解矣。咱會當:
exploit.phar
檔案。exploit.phar
壓縮做 exploit.phar.gz
。.gz
檔案,內容早就無 <?
抑是 __HALT_COMPILER();
這寡關鍵字,所以會當順利通過 upload.php
的檢查。include('exploit.phar.gz')
,PHP 就會佇底層恬恬仔共伊解壓縮,才閣執行內底的惡意 code,造成 RCE!咱會記得講咱檔案名共焦需要有「.phar」這五字,include
就tsua̋nn會共彼个檔案當做 phar 處理、引入來。所以毋管講是:
./foo.phar.gzip
./foo.pharzzzzz
./foo.pharmeow/bar/baz.png
攏會當予 PHP 共當做 PHAR 處理!
咱愛來加讀寡 PHP source,予 PHP 閣再偉大 :D
明仔載咱就轉來繼續講一寡 conferences 研究的物件囉