手把手玩 OAuth Labs(一)
這是一篇 OAuth 漏洞的實戰入門紀錄,我用 cyllective 的 oauth-labs 當靶場,從環境搭建(含 WSL2 踩到的坑)一路寫到 Lab 01 的完整分析,適合對 OAuth 攻擊有興趣、但還沒實際打過的人。
為什麼選 oauth-labs
OAuth 是現在幾乎每個「用 Google / GitHub 登入」背後的協定,實作錯了就是帳號接管等級的洞,但 OAuth 的攻擊面對新手不太友善——流程繞、名詞多,光看文件很難有感。
cyllective 的 oauth-labs 把常見的 OAuth 錯誤實作拆成六個獨立的 lab,每個只藏一個漏洞,而且可以讀原始碼(白箱),對想建立 OAuth 攻擊直覺的人來說,這是很理想的起點。
六個 lab 的主題分別是:
- Lab 00:純 playground,沒有漏洞,用來熟悉流程
- Lab 01:不穩定的身分識別值(unstable claims)← 本篇
- Lab 02:完全不驗
redirect_uri的 open redirect - Lab 03:只驗 domain 的 open redirect
- Lab 04:JWT 簽章根本沒驗
- Lab 05:JWT
jkuclaim 處理不當
環境搭建:Docker 很簡單,WSL2 才是關卡
靶場本身很好起,但我在 WSL2(Windows 上的 Kali)踩了一連串網路的坑,這段值得記錄,因為任何「綁在特定內部 IP 的 Docker 靶場」都會遇到。
起靶場
前置只有兩步:改 hosts、跑 make。先把 README 給的那一整串網域寫進 /etc/hosts(它用 172.16.16.0/24 這個網段,注意別跟你現有網路撞到),然後:
git clone https://github.com/cyllective/oauth-labs
cd oauth-labs
make config && make docker && make labs
docker compose ps 看到一排 server-0x / client-0x 都 Up 就成功了。
坑一:瀏覽器在 Windows,hosts 卻加在 WSL
第一個 ERR_NAME_NOT_RESOLVED 就卡住了,原因很經典:我 sudo tee -a /etc/hosts 加的是 WSL(Linux)的 hosts,只有 Linux 裡的程式查得到;但我用的是 Windows 的瀏覽器,它查的是 Windows 自己的 hosts,那裡根本沒有 oauth.labs。
兩個系統各有各的 hosts,這是 WSL2 開發最常見的認知落差之一。
坑二:服務綁死在 Docker 內部 IP,Windows 連不進去
就算 Windows 的 hosts 也加了,還是連不到。回頭看 docker compose ps,caddy(反向代理)的綁定是:
172.16.16.1:80->80/tcp, 172.16.16.1:443->443/tcp
它只綁在 172.16.16.1——這是 Docker 網路的 gateway,存在於 WSL 內部,Windows 路由不過去,
檢查 compose 檔證實了這點:
caddy:
ports:
- "172.16.16.1:80:80"
- "172.16.16.1:443:443"
治本的做法是讓它綁到全介面。把那個 IP 前綴拿掉:
sed -i 's|"172.16.16.1:80:80"|"0.0.0.0:80:80"|; s|"172.16.16.1:443:443"|"0.0.0.0:443:443"|' docker-compose.yaml
make labsdown && make labs
重啟後 caddy 變成 0.0.0.0:443->443,就綁到 WSL 的所有介面了,接著在 Windows 的 hosts 把所有 *.oauth.labs 指到 WSL 的 eth0 IP(用 ip addr show eth0 查,通常是 172.2x.x.x 那個),Windows 的瀏覽器與 Burp 就能連進來。
提醒:WSL 的 eth0 IP 重開機後可能改變,連不上時重查、更新 Windows hosts 即可。
Lab 01:找出「client 拿什麼認你」
環境好了,進 Lab 01,攻擊目標寫在關卡說明:取得 admin 使用者的資源存取權,漏洞主題是「unstable claims」。
打漏洞之前,我習慣先掛上 Burp、走一遍正常流程——不知道正常長怎樣,就不知道哪裡不正常,
先看 authorization request
在 server-01 註冊帳號、從 client-01 發起登入,攔到的 authorization request(URL decode 後)是這樣:
/oauth/authorize?
client_id=<uuid>
code_challenge=<...>
code_challenge_method=S256 ← 有 PKCE
redirect_uri=https://client-01.oauth.labs/callback
response_type=code ← authorization code flow
scope=read:profile
state=<...>
這是標準的 authorization code flow 加上 PKCE,而且 redirect_uri 是寫死的。這告訴我:這關的漏洞不在 redirect_uri 或 code 攔截,要往別的方向找。
關鍵在「client 認人的那一頁」
真正的線索出現在登入成功後、client-01 的 profile 頁,它的回應長這樣(節錄):
<h1>Thanks for signing in!</h1>
<p>To change your profile details, please visit https://server-01.oauth.labs</p>
<label>Firstname</label> <input value="" readonly>
<label>Lastname</label> <input value="" readonly>
<label>Email</label> <input value="" readonly>
<p>... hrm something seems to be missing.</p>
這一頁給了兩個明示:
- Firstname / Lastname / Email 三個欄位全空,還自嘲「something seems to be missing」——代表 client 想顯示這些資料,但拿不到(因為我註冊時沒填),client 依賴 server 傳來的這些欄位。
- 它直接叫你去 server-01 改資料——代表你的身分資料(claim)是在 server-01 那邊、由你自己填的、而且可以改。
推理:哪個欄位被拿來當「身分」?
到 server-01 的 profile 編輯頁,可以改的欄位正是 firstname、lastname、email 三個。
這三個裡,哪個最可能被 client 當成「你是誰」的唯一識別?
firstname/lastname比較像「顯示用的名字」,一般不會拿來認人。email則常被系統當成唯一身分——很多網站就是用 email 認人的。
再加上這關的名字是「unstable claims(不穩定的識別值)」,而 email 剛好是「使用者能自己改」的欄位——題目名稱本身就在暗示:client 蠢在拿了一個『可變』的東西當『身分』。
這個判斷不是憑空猜的,是把四條觀察串起來推出來的:client 依賴 server 的欄位 → 欄位由使用者自填可改 → 三個欄位裡 email 最像身分識別 → 關卡名稱印證。這一連串「觀察 → 推理」就是漏洞分析本身。
驗證機制,再打目標
我沒有直接跳去填 admin 的 email,而是先做一個對照實驗:把 email 填一個測試值,存檔,重新登入 client-01,看它的 profile 有沒有變化,結果 client-01 的 profile 從「全空」變成顯示了我填的 email——證實了 client-01 確實用 email 認人。
先證明機制、再打目標,這個順序很重要:如果直接填 admin email 然後失敗,你會分不清是「email 根本不是身分識別」還是「admin email 被某種檢查擋掉」。
確認機制後,剩下的就是把自己的 email 改成 admin 的、重新登入,這關的 server 沒有對 email 做唯一性檢查,所以能直接改成 admin 的 email;client 用 email 認人,於是重新登入後就被誤認成 admin。
這個漏洞的本質
一句話總結:
client 拿了 OAuth 身分證明裡「使用者能自己修改的 email」當唯一身分,而不是用不可變的 sub;server 又沒鎖 email 唯一性,兩者相加,任何人都能冒充任何人。為什麼是 OAuth 漏洞
OAuth 流程結束時,authorization server 會回給 client 一組使用者資訊(claims),裡面通常有:
sub: 永久不變的內部 ID(伺服器指派,使用者控制不了)
email: 使用者能自己改
name: 使用者能自己改
OAuth/OIDC 標準明確規定:client 要用 sub 來辨識使用者,正因為它穩定、唯一、不可控,這關的 client 偷懶用了 email——一個「不穩定的 claim」——身分驗證的地基就塌了,這不是靶場才有的紙上談兵,真實世界一堆「用 email 認人」的整合都栽在這裡。
它其實是你熟悉的老朋友
如果你做過 web 滲透,這個漏洞的骨架你早就認識:
系統用「使用者可以控制的值」來做「安全決策」。
- cookie 裡的
role=user,你改成role=admin - URL 的
?user_id=123,你改成?user_id=1← 這就是 IDOR - 這關的 email,你改成 admin 的 ← 換了 OAuth 的外衣
所以「改 email 變 admin」不是一條通用公式,通用的是那個問法:「這個決定我身分/權限的值,是不是我能控制的?能的話,改成別人的會怎樣?」 這跟 IDOR/BOLA 是同一顆腦子。
小結
Lab 01 沒有花俏的技巧,但它把一個很重要的觀念講得很清楚:OAuth 的安全,很大一部分取決於 client『選擇相信身分證明裡的哪個欄位』,選錯欄位,再完整的 PKCE、再嚴謹的流程都保護不了你。
本文為個人學習紀錄,所有操作皆在本機自建的靶場環境進行,文中不含 flag 與具體識別值,鼓勵讀者自行部署 oauth-labs 動手實作。
Member discussion