คลัง
web

Cross-Site Request Forgery (CSRF)

CSRF หลอกให้เบราว์เซอร์ของเหยื่อส่ง request ที่มี side-effect ไปยังเว็บที่เหยื่อล็อกอินอยู่ โดยอาศัยว่าเบราว์เซอร์แนบ cookie ให้อัตโนมัติ บทนี้อธิบายเงื่อนไขที่เกิด, การสร้าง PoC (form/GET/XHR), บทบาทของ SameSite, และการ bypass การป้องกันที่ตั้งหลวม (token ไม่ผูก session, ลบ token, method/Referer หลวม, JSON content-type, CORS misconfig)

Intermediate#csrf#samesite#csrf-token#cookie#cors#client-side#web#ctf

1. หลักการ & เงื่อนไข

เบราว์เซอร์แนบ cookie ของเว็บปลายทางให้ ทุก request โดยอัตโนมัติ แม้ request นั้นถูกสั่งจากเว็บอื่น ถ้าเว็บเป้าหมายตัดสิน 'ใคร' จาก cookie อย่างเดียวโดยไม่มี token ยืนยันว่า request มาจากหน้าเว็บของตัวเอง ผู้โจมตีสร้างหน้าเว็บที่ยิง request แทนเหยื่อได้ — เปลี่ยนอีเมล, โอนเงิน, เปลี่ยนรหัส, ผูกบัญชี

CSRF: cookie ถูกแนบอัตโนมัติจากเว็บผู้โจมตี
evil.comauto-submit form browser เหยื่อแนบ cookie เอง target.comเชื่อ cookie 1. เหยื่อเปิดหน้า 2. POST + cookie เหยื่อ target เชื่อ cookie อย่างเดียว → ทำ action แทนเหยื่อ
  • เงื่อนไขที่ CSRF เกิด: (1) action มี side-effect, (2) พึ่ง cookie session อย่างเดียว, (3) ไม่มี token/parameter ที่คาดเดาไม่ได้
  • ถ้าใช้ Authorization header / token ที่ JS ต้องแนบเอง → มักไม่โดน CSRF (เว็บอื่นแนบ header นั้นข้าม origin ไม่ได้)
  • SameSite=Lax (ค่า default ของเบราว์เซอร์สมัยใหม่) ลด CSRF ลงมาก แต่ยังมีช่องผ่าน top-level GET และเทคนิคอื่น

2. สร้าง PoC

CSRF ผ่าน form (auto-submit) — content-type ปกติ
<form action="https://target/change-email" method="POST" id="f">
  <input type="hidden" name="email" value="attacker@evil.com">
</form>
<script>document.getElementById('f').submit()</script>
เหยื่อแค่เปิดหน้านี้ขณะล็อกอิน target → request ส่งพร้อม cookie ของเหยื่อ form ส่งได้เฉพาะ content-type: application/x-www-form-urlencoded, multipart/form-data, text/plain (simple request ไม่ trigger preflight)
GET-based (ถ้า action ยอมรับ GET)
<img src="https://target/change-email?email=attacker@evil.com">
<!-- หรือ top-level navigation ผ่าน SameSite=Lax -->
<script>location = 'https://target/action?x=1'</script>
XHR/fetch (เมื่อ CORS ตั้งหลวม)
<script>
fetch('https://target/api/change-email', {
  method: 'POST',
  credentials: 'include',           // แนบ cookie ข้าม origin
  headers: {'Content-Type':'text/plain'},  // เลี่ยง preflight
  body: JSON.stringify({email:'attacker@evil.com'})
})
</script>
อ่าน response ได้ก็ต่อเมื่อ target ตั้ง ACAO สะท้อน origin + ACAC:true (CORS misconfig) แต่ 'ส่ง' request มีผลได้แม้อ่าน response ไม่ได้
Burp มีฟีเจอร์ Engagement tools → Generate CSRF PoC สร้าง HTML PoC จาก request ที่ดักได้ให้อัตโนมัติ ใช้ทดสอบเร็ว

3. SameSite — ด่านแรกและช่องที่เหลือ

SameSiteพฤติกรรมช่องที่เหลือ
Strictไม่แนบ cookie ทุก cross-siteต้องหา on-site gadget (XSS/open redirect)
Lax (default)แนบเฉพาะ top-level GET navigationCSRF ผ่าน GET, หรือ method override
Noneแนบทุก cross-site (ต้องมี Secure)CSRF ได้เต็ม → ต้องพึ่ง token
  • Lax bypass ผ่าน GET: ถ้า state-changing action รับ GET หรือ method override → top-level navigation แนบ cookie ได้
  • Lax 2-minute window: บางเบราว์เซอร์เคยแนบ cookie แบบ Lax กับ POST ในช่วงสั้นหลังตั้ง cookie (พฤติกรรมเปลี่ยนตามเวอร์ชัน)
  • Sibling subdomain: SameSite มองที่ registrable domain — subdomain ที่ถูก compromise/มี XSS นับเป็น same-site
  • Strict bypass: ต้องมี gadget บนโดเมนเดียวกัน (client-side redirect, XSS) ให้ยิงแบบ same-site

4. Bypass การป้องกันที่หลวม

แม้มี CSRF token ก็ยัง bypass ได้ถ้า implementation หลวม — ไล่ทดสอบทีละสมมติฐาน

จุดอ่อนทดสอบผล
token ไม่ผูก sessionใช้ token ของ attacker เองกับ session เหยื่อผ่าน = bypass
token เช็คเฉพาะเมื่อมีลบ parameter token ออกทั้งหมดผ่าน = bypass
token เช็คต่อ methodเปลี่ยน POST→GET (token อาจไม่ถูกตรวจใน GET)ผ่าน = bypass
token อยู่ใน cookie ด้วย (double-submit หลวม)ตั้ง cookie+param เท่ากันจาก subdomainผ่าน = bypass
Referer เช็คหลวมวาง target ใน path/param ของ evil (evil.com/target.com)ผ่าน = bypass
Referer เช็คเฉพาะเมื่อมีตัด Referer ด้วย meta referrer / rel=noreferrerผ่าน = bypass
ตัด Referer เพื่อ bypass การเช็คแบบ 'เฉพาะเมื่อมี'
<meta name="referrer" content="no-referrer">
<form action="https://target/action" method="POST">...</form>
<!-- request ออกไปโดยไม่มี Referer → ถ้า server เช็คเฉพาะเมื่อ Referer มี → ผ่าน -->

JSON endpoint: API ที่รับเฉพาะ Content-Type: application/json ดูปลอดภัยขึ้นเพราะ form ส่ง json ไม่ได้ และ fetch ที่ตั้ง json จะ trigger CORS preflight แต่ถ้า backend ยอมรับ text/plain หรือไม่ตรวจ content-type จริง → ส่ง JSON ผ่าน text/plain (simple request ไม่ preflight) ได้

CSRF กับ CORS สับสนกันบ่อย: CORS คุมการ 'อ่าน response' ข้าม origin — ไม่ได้กันการ 'ส่ง' request ดังนั้น CORS ที่ถูกต้องไม่ได้ป้องกัน CSRF และ CORS ที่ตั้งหลวม (สะท้อน origin + credentials:true) กลับเปิดช่องอ่านข้อมูลข้าม origin เพิ่ม

5. Decision flow

action มี side-effect — ทดสอบ CSRF ตามนี้
auth พึ่ง cookie อย่างเดียวไหม?
ใช้ Authorization headerมักกัน CSRF ได้ → เลิก
SameSite ของ session cookie?
None/ไม่ตั้งไปเช็ค token
Laxลอง GET/top-level nav
Strictหา on-site gadget (XSS/redirect)
มี CSRF token ไหม?
ไม่มีPoC form/GET ได้เลย
มีลบ token / ใช้ token attacker / สลับ method
เช็ค Referer/Origin หลวมไหม?
ตัด Referer / วาง target ใน path
content-type json? → ลอง text/plain (เลี่ยง preflight)
สร้าง PoC → ยืนยัน action ทำงานด้วย session เหยื่อ

6. ตัวอย่าง CTF & Real-world

CTF (PortSwigger): เปลี่ยนอีเมลมี CSRF token แต่ 'token ไม่ผูก session' — เอา token จาก session ของเราเองใส่ใน PoC ที่ยิงด้วย cookie เหยื่อ → ผ่าน อีกโจทย์ token ถูกตรวจเฉพาะใน POST พอเปลี่ยนเป็น GET (method override) ระบบข้ามการเช็ค token ทั้งหมด

Real-world: API รับ application/json แต่ backend parse body โดยไม่บังคับ content-type — ส่งด้วย <form> ที่ตั้ง enctype=text/plain และประกอบ body ให้เป็น JSON ที่ valid ({"email":"x@e.com","ignore":"=1"}) → CSRF สำเร็จโดยไม่ต้องพึ่ง CORS

7. ข้อผิดพลาด & การป้องกัน

  • สับสน CSRF กับ CORS: CORS ไม่ได้กัน CSRF — คนละเรื่อง
  • สรุปว่า json = ปลอดภัย: ต้องตรวจว่า backend บังคับ content-type จริงไหม
  • ลืมทดสอบ token binding: token มีอยู่ ≠ ผูก session/ถูกตรวจจริง
  • มองข้าม SameSite bypass: Lax ยังโดน GET/method override
การป้องกัน (blue team): CSRF token ที่ผูกกับ session + ตรวจทุก state-changing request (synchronizer pattern), ตั้ง cookie SameSite=Lax/Strict, ตรวจ Origin header (แม่นกว่า Referer และตัดยาก), บังคับ content-type สำหรับ JSON API, ใช้ custom header ที่ต้องตั้งด้วย JS same-origin, และไม่ยอมรับ state-changing action ผ่าน GET

8. Quick Reference

  • เกิดเมื่อ: side-effect + พึ่ง cookie อย่างเดียว + ไม่มี token
  • PoC: auto-submit form, (GET), fetch credentials:include
  • SameSite: Strict→หา gadget, Lax→GET/method override, None→ต้องมี token
  • bypass token: ลบ token, ใช้ token attacker, สลับ method, double-submit หลวม
  • Referer หลวม: ตัด Referer / วาง target ใน path ของ evil
  • JSON: ลอง text/plain (simple request เลี่ยง preflight)
  • CORS ≠ ป้องกัน CSRF; Authorization header = มักกันได้
  • ป้องกัน: token ผูก session + SameSite + ตรวจ Origin + บังคับ content-type

🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ

สมมติเจอ action ที่มี side-effect (เปลี่ยนอีเมล/โอนเงิน) มีแค่ Kali เปล่าๆ ทำตามนี้ทีละขั้นเพื่อเช็คว่ามี CSRF ไหม

  1. 1เปิด Burp Suite ตั้ง proxy แล้วทำ action ที่มี side-effect ผ่านหน้าเว็บ ให้ Burp ดักไว้ 1 request
  2. 2เช็ค Set-Cookie ของ session ด้วย curl -sI ดูว่า SameSite เป็น Strict/Lax/None
  3. 3ดูว่า request พึ่ง cookie อย่างเดียวไหม (ไม่มี Authorization header หรือ custom token ที่ JS ต้องแนบเอง)
  4. 4ใน Burp คลิกขวา request → Engagement tools → Generate CSRF PoC เพื่อสร้าง HTML form อัตโนมัติ
  5. 5เซฟไฟล์ PoC แล้วเปิดด้วย python3 -m http.server ในเบราว์เซอร์ที่ยัง login เว็บเป้าหมายอยู่ ดูว่า action ทำงานไหม
  6. 6ถ้ามี CSRF token ลองลบ parameter token ออกทั้งหมดแล้วส่งซ้ำผ่าน Repeater
  7. 7ลองใช้ token จาก session ของตัวเองแทนของเหยื่อ (เช็คว่าผูก session จริงไหม)
  8. 8ลองเปลี่ยน POST→GET ดูว่า token ยังถูกตรวจไหม
  9. 9ถ้า endpoint เป็น JSON ลองเปลี่ยน Content-Type เป็น text/plain แล้วประกอบ body ให้ยังเป็น JSON ที่ valid
ตัดสินใจ: action นี้มี CSRF จริงไหม
auth พึ่ง cookie อย่างเดียวไหม?
❌ ใช้ Authorization header ที่ JS ต้องแนบเอง→ มักกัน CSRF ได้แล้ว หมดทางนี้
✅ พึ่ง cookie อย่างเดียว→ สร้าง PoC ทดสอบต่อ
สร้าง CSRF PoC ด้วย Burp (Generate CSRF PoC) แล้วเปิดทดสอบ
action ทำงานไหม?
✅ action สำเร็จ→ ยืนยัน CSRF เดี่ยวๆ ได้เลย
❌ ไม่ทำงาน (token กัน)→ ลอง bypass token
ลบ token / ใช้ token ตัวเอง / สลับ method / ตัด Referer
✅ ผ่านสักท่า→ ยืนยัน CSRF ผ่านการ bypass
❌ กันแน่นทุกทาง→ ดู SameSite: Strict ต้องหา on-site gadget
สร้าง PoC สำเร็จ → ยืนยัน action ทำงานด้วย session เหยื่อ
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
ดักจับ request + สร้าง PoCBurp Suite Community (Generate CSRF PoC)--
ตรวจ cookie SameSitecurl--
โฮสต์ PoC HTML ทดสอบpython3 -m http.server--
ทดสอบ bypass content-type/methodBurp Repeater--
หา gadget เสริม (XSS) เมื่อ Strict---
🚑 ถ้าตันสนิท ลองท่าถัดไป: XSS — ถ้า SameSite=Strict ต้องหา gadget บนโดเมนเดียวกันก่อนถึงจะยิง CSRF ได้ · Open Redirect — ใช้เป็น gadget same-site หรือ chain ให้ token รั่วผ่าน Referer · OAuth Attacks — ถ้า action ที่ไม่มี state คือการผูกบัญชี social login · Session Testing — ตรวจ cookie attribute อื่นเพิ่มเติมนอกจาก SameSite

โน้ตของฉัน

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