33 คะแนน โดย GN⁺ 2025-03-04 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ตอนที่เริ่มพิจารณาเรื่อง Cross-Site Request ใหม่ ๆ ก็ยังไม่เข้าใจในตอนแรกว่าทำไมถึงต้องมีทั้งการป้องกัน CSRF และ CORS พร้อมกัน แต่ถ้าจะอธิบายเรื่องนี้ต้องใช้คำค่อนข้างมาก

CSRF และ CORS

  • CSRF (Cross-Site Request Forgery)
    • ในอดีตเคยพบได้บ่อย แต่ปัจจุบันเว็บเฟรมเวิร์กส่วนใหญ่มีการป้องกันมาให้เป็นค่าเริ่มต้น จึงแทบไม่ค่อยเป็นปัญหาแล้ว
    • วิธีโจมตี: หลอกให้ผู้ใช้คลิกฟอร์มบางอย่างบนเว็บไซต์อันตราย เพื่อส่งคำขอแบบข้ามไซต์
    • วิธีป้องกัน: ตรวจสอบว่าคำขอนั้นไม่ได้ไหลเข้ามาจากเว็บไซต์อื่น
  • CORS (Cross-Origin Resource Sharing)
    • เป็นส่วนหนึ่งของสเปก HTTP ที่กำหนดวิธีอนุญาตคำขอข้ามไซต์บางประเภท
    • ใช้ preflight request และ response header เพื่อระบุว่าอนุญาตให้ส่งคำขอจาก origin ใดได้บ้าง

ถ้าอย่างนั้น คำขอข้ามไซต์ถูกอนุญาตเป็นค่าเริ่มต้นจนต้องมี CSRF protection หรือจริง ๆ แล้วถูกบล็อกเป็นค่าเริ่มต้นและต้องใช้ CORS เพื่อเปิดให้ใช้งาน? คำตอบคือ ทั้งสองอย่าง

พฤติกรรมพื้นฐาน

  • นโยบาย same-origin (Same-origin policy)
    • เป็นนโยบายความปลอดภัยที่เบราว์เซอร์บังคับใช้
    • โดยทั่วไป การเขียน (Write) แบบข้ามไซต์ทำได้, แต่ การอ่าน (Read) ถูกห้าม
    • ตัวอย่างเช่น เบราว์เซอร์อนุญาตคำขอ POST ผ่านฟอร์มได้ แต่ไม่อนุญาตให้อ่าน response
  • นโยบายคุกกี้ SameSite
    • ในปี 2019 พฤติกรรมเริ่มต้นของคุกกี้ได้เปลี่ยนไป
    • เดิมทีคุกกี้จะถูกส่งไปพร้อมคำขอข้ามไซต์เสมอ
    • มีการเพิ่มแอตทริบิวต์ SameSite ใหม่ และเปลี่ยนค่าเริ่มต้นเป็น Lax
    • ณ ปี 2025 96% ของเบราว์เซอร์รองรับแอตทริบิวต์ SameSite และ 75% รองรับค่าเริ่มต้นใหม่ (Lax)
    • อย่างไรก็ตาม Safari ไม่ได้นำค่าเริ่มต้นนี้มาใช้ และ UCBrowser ก็ยังไม่รองรับ
  • ความแตกต่างระหว่าง Site กับ Origin
    • Origin: การรวมกันของ โปรโตคอล + ชื่อโฮสต์ + พอร์ต
    • Site: การรวมกันของ โปรโตคอล + top-level domain + 1 (ไม่สนใจซับโดเมนและพอร์ต)

CORS

  • CORS คือวิธีเปิดข้อยกเว้นของนโยบาย same-origin ให้กับ origin บางแห่ง
  • ก่อนส่งคำขอ เบราว์เซอร์จะส่ง preflight request แบบ OPTIONS ก่อน
  • เซิร์ฟเวอร์จะกำหนดกฎการอนุญาตผ่าน response header (ใช้ header กลุ่ม Access-Control-*)
  • ประเภทคำขอที่ CORS มีผลบังคับใช้:
    • fetch และ XMLHttpRequest
    • เว็บฟอนต์
    • WebGL texture
    • รูปภาพ/เฟรมวิดีโอที่วาดด้วย drawImage ใน canvas
    • รูปภาพที่ใช้ในพร็อพเพอร์ตี CSS shape-outside
  • แต่การส่งฟอร์มเป็นข้อยกเว้นที่ CORS ไม่ครอบคลุม
    • แท็ก <form> ของ HTML 4.0 อนุญาตคำขอข้ามไซต์มานานมากแล้ว
    • ดังนั้นเซิร์ฟเวอร์รุ่นเก่าจึงควรถูกออกแบบมาให้ป้องกันการโจมตีแบบ CSRF อยู่แล้ว
    • หากต้องการแชร์ response เซิร์ฟเวอร์ต้องตั้งค่า Access-Control-Allow-Origin แต่ตัวคำขอเองยังคงถูกรับได้แม้ไม่มี preflight request

คำถาม: นโยบาย SameSite กับวิธีนี้รักษาความสอดคล้องกันอย่างไร?

วิธีป้องกัน CSRF

  • คำขอแบบเขียนข้ามไซต์ได้รับอนุญาต แต่ response จะไม่ถูกแชร์
    • เว็บไซต์ส่วนใหญ่ไม่ได้ต้องการอนุญาตการเขียนแบบข้ามไซต์
  • แนวทางป้องกัน CSRF มาตรฐาน
    • ใส่ CSRF token รายผู้ใช้ไว้ในคำขอเพื่อนำไปตรวจสอบ
    • วิธีการ:
      • การส่งฟอร์ม: เพิ่มโทเคนด้วย hidden input
      • คำขอจาก JS: เก็บไว้ในคุกกี้หรือแท็ก meta แล้วส่งไปใน request header หรือพารามิเตอร์
  • คำขอจาก JS โดยปกติถูกบล็อกไม่ให้ข้ามไซต์อยู่แล้ว
    • แต่ยังอนุญาตสำหรับคำขอแบบ same-site
    • หากใส่ CSRF token ก็สามารถตรวจสอบคำขอทั้งหมดด้วยวิธีเดียวกันได้
  • ข้อดีด้านความปลอดภัยเพิ่มเติม
    • ทำงานบนสมมติฐานว่าเบราว์เซอร์จะบล็อกการอ่าน response เป็นค่าเริ่มต้น
    • ปลอดภัยกว่าการตรวจสอบ header Origin เพียงอย่างเดียว

คำถาม: ในบางเฟรมเวิร์กมีการเปลี่ยน CSRF token เป็นระยะ ๆ ทำไมจึงเป็นเช่นนั้น?

บทบาทของเบราว์เซอร์

  • หัวใจสำคัญของความปลอดภัยบนเว็บขึ้นอยู่กับว่า เบราว์เซอร์เชื่อถือได้หรือไม่
  • เบราว์เซอร์จะ:
    • บังคับใช้นโยบาย same-origin
    • บล็อกไม่ให้อ่าน response หากไม่ได้รับอนุญาต
    • ตัดสินใจว่าจะใช้ค่าเริ่มต้น SameSite=Lax หรือไม่
    • ติดตั้งใช้งาน CORS และส่ง preflight request อย่างปลอดภัย

เราจำเป็นต้องเชื่อถือเบราว์เซอร์ที่เราใช้งานอยู่

สรุป

  • หาก SameSite=Lax ได้รับการรองรับครบ 100% ในทุกเบราว์เซอร์ ความปลอดภัยก็จะสูงขึ้นอีก
    แต่ในปัจจุบันก็ยังคงมีข้อยกเว้นที่อนุญาตเฉพาะ คำขอ POST แบบข้ามไซต์ อยู่
  • ดังนั้นนักพัฒนาจึงยังต้องคำนึงถึงการป้องกัน CSRF อย่างต่อเนื่อง

"อินเทอร์เน็ตกำลังปลอดภัยขึ้นเรื่อย ๆ แต่ในขณะเดียวกัน ความเข้ากันได้กับอดีตก็กำลังลดลงเรื่อย ๆ เช่นกัน"

แหล่งที่มา

  1. Same-origin policy
  2. caniuse SameSite cookie attribute
  3. OWASP CSRF cheatsheet
  4. CORS wiki with requirements
  5. CORS spec
  6. CORS on MDN
  7. Preflight request
  8. Origin request header
  9. Origin and Site

1 ความคิดเห็น

 
GN⁺ 2025-03-04
ความเห็นจาก Hacker News
  • CORS เป็นกลไกที่เซิร์ฟเวอร์ใช้บอกเบราว์เซอร์อย่างชัดเจนว่าคำขอข้ามต้นทางแบบใดบ้างที่สามารถอ่านการตอบกลับได้

    • โดยค่าเริ่มต้น เบราว์เซอร์จะบล็อกไม่ให้สคริปต์ข้ามต้นทางอ่านการตอบกลับ
    • หากไม่ได้รับอนุญาตอย่างชัดเจน โดเมนที่ส่งคำขอจะไม่สามารถอ่านการตอบกลับได้
    • ตัวอย่างเช่น สคริปต์จาก evil.com อาจส่งคำขอไปที่ bank.com/transactions เพื่อพยายามอ่านประวัติธุรกรรมของเหยื่อ
    • เบราว์เซอร์จะยอมให้คำขอไปถึง bank.com แต่จะบล็อกไม่ให้ evil.com อ่านการตอบกลับ
  • การป้องกัน CSRF ช่วยป้องกันไม่ให้คำขอข้ามต้นทางที่เป็นอันตรายดำเนินการโดยไม่ได้รับอนุญาตในนามของผู้ใช้ที่ยืนยันตัวตนแล้ว

    • ตัวอย่างเช่น สคริปต์จาก evil.com อาจส่งคำขอเพื่อให้ bank.com ดำเนินการบางอย่าง (เช่น โอนเงินไปที่ bank.com/transfer?from=victim&to=hacker)
    • การป้องกัน CSRF ฝั่งเซิร์ฟเวอร์ของ bank.com จะปฏิเสธสิ่งนี้ (น่าจะเพราะคำขอไม่มีโทเค็น CSRF ลับรวมอยู่ด้วย)
  • การป้องกัน CSRF คือเรื่องของการป้องกันการเขียน ส่วน CORS คือเรื่องของการป้องกันการอ่าน

  • คำขอที่เริ่มต้นจาก JS จะไม่อนุญาตให้ข้ามไซต์โดยค่าเริ่มต้น

    • สามารถเริ่มคำขอข้ามไซต์ได้ด้วย fetch() หากใช้เฉพาะเฮดเดอร์ที่ได้รับอนุญาต
  • คิดว่าน่าจะมีคำอธิบายที่ดีกว่านี้สำหรับหัวข้อนี้

    • มีการแชร์ลิงก์ไปยังบล็อกที่เกี่ยวข้อง
  • คำตอบต่อคำถามในโพสต์บล็อก

    • องค์ประกอบ <form> ของ HTML 4.0 สามารถส่ง simple request ไปยังต้นทางใดก็ได้
    • มีคำถามว่าประเด็นนี้สอดคล้องกับโครงการริเริ่ม SameSite อย่างไร
  • ในปี 2022 มีการเพิ่มย่อหน้าในบทความ CORS ของ MDN เพื่อชี้แจงที่มาของคำว่า "simple request"

    • ก่อนหน้านี้มีเพียงการระบุว่าไม่ได้กล่าวถึงไว้ในสเปก fetch
    • ไม่มีการกล่าวถึงกรณีในปี 2019 ที่ฟีเจอร์ป้องกัน CSRF ของเบราว์เซอร์รองรับ SameSite=Lax หรือกำหนดเป็นค่าเริ่มต้น
  • น่าสับสนที่ SameSite ถูกเพิ่มเข้ามาโดยเป็นอิสระจาก CORS preflight

    • สงสัยว่าทำไมผู้พัฒนาเบราว์เซอร์จึงไม่บังคับให้คำขอ POST ข้ามต้นทางทั้งหมดต้องมี preflight
  • อาจคิดว่าปลอดภัยแม้ไม่ใช้ csrf แต่บางไลบรารี (เช่น django rest framework) สามารถประมวลผลฟอร์ม HTML ได้หากมีการตั้งค่า content-type header

    • สิ่งนี้ทำให้สามารถโพสต์ฟอร์มไปยังไซต์ของผู้ใช้เพื่อส่งคำขอแทนผู้ใช้นั้นได้
  • คำถามเกี่ยวกับเหตุผลที่ CSRF token มีการหมุนเปลี่ยน

    • OWASP บอกว่าวิธีนี้ปลอดภัยกว่า แต่ไม่ค่อยเข้าใจเหตุผล
  • ขอผังงานสำหรับหัวข้อที่ซับซ้อนนี้

    • ต้องการแพลตฟอร์มแอปพลิเคชันและชุดมาตรฐานใหม่
  • สิ่งเหล่านี้ไม่รองรับการติดตามเพื่อวินิจฉัยที่ทำได้ง่าย

    • เคยเจอข้อผิดพลาดที่ไม่ชัดเจนหลายครั้งกับกรณีใช้งานที่ถูกต้องแต่ตั้งค่าไม่เหมาะสม
  • ไม่เข้าใจว่าก่อนที่ CORS จะมีขึ้นมา ทำไมจึงส่งคำขอไปยังเอนด์พอยต์ใดก็ได้ที่ไม่ใช่ต้นทางของหน้าได้ แต่กลับดูการตอบกลับไม่ได้

    • สงสัยว่านี่เป็นสิ่งที่ติดมาในสเปกโดยบังเอิญ หรือเป็นการออกแบบโดยตั้งใจเพราะคาดการณ์เรื่อง XSS ไว้ หรือเพราะเบราว์เซอร์รายหลักทำแบบนั้นก่อนแล้วตัวอื่นจึงทำตาม
  • ความสับสนเกี่ยวกับการป้องกัน CSRF

    • สงสัยว่ามีวิธีใดที่ป้องกันไม่ให้ผู้โจมตีไปเอา CSRF token จาก goodsite.com มาใส่ไว้ใน badsite.com แล้วหลอกให้ Alice ส่งคำขอจาก badsite.com ไปยัง goodsite.com ได้