CORS ไม่ใช่กำแพงกันแฮกเกอร์: วิธีตั้งค่าให้ปลอดภัยและไม่เปิดอ่านข้อมูล
CORS เป็นกติกาที่กำหนดว่าเบราว์เซอร์จะยอมให้เว็บใดอ่าน response จาก API ได้ ไม่ใช่กลไกป้องกันการโจมตีโดยตรง ดังนั้นหากตั้งค่า Access-Control-Allow-Origin กว้างเกินไป โดยเฉพาะเมื่อใช้ร่วมกับ credentials ก็อาจเปิดช่องให้เว

CORS ไม่ใช่กำแพงกันแฮกเกอร์: วิธีตั้งค่าให้ปลอดภัยและไม่เปิดอ่านข้อมูล
หลายคนมักเข้าใจว่า CORS เป็นเหมือนกำแพงที่ช่วยกันแฮกเกอร์ไม่ให้เข้าถึง API แต่ในความเป็นจริง CORS คือกติกาที่ server ประกาศให้ browser รับรู้ว่า เว็บไซต์ใดมีสิทธิ์อ่าน response จาก API ของเราได้บ้าง
ประเด็นสำคัญคือ CORS ไม่ได้หยุดทุก request ที่เข้ามาหา API แต่ควบคุมว่า response จะถูกเปิดให้อ่านได้หรือไม่ หากตั้งค่าผิด โดยเฉพาะกับ endpoint ที่มีข้อมูลสำคัญ ก็อาจกลายเป็นช่องโหว่ร้ายแรงได้
CORS ทำหน้าที่อะไร และไม่ได้ทำหน้าที่อะไร
CORS หรือ Cross-Origin Resource Sharing เป็นกลไกที่เบราว์เซอร์ใช้ตัดสินใจว่า JavaScript จาก origin หนึ่ง จะสามารถอ่านผลลัพธ์จากอีก origin หนึ่งได้หรือไม่
กล่าวให้ชัดขึ้นคือ
- CORS ไม่ได้เป็นไฟร์วอลล์
- CORS ไม่ได้ป้องกันไม่ให้ attacker ส่ง request ได้เสมอไป
- CORS ควบคุมเรื่อง การอ่าน response ผ่าน browser เป็นหลัก
ดังนั้น ความเข้าใจที่ถูกต้องคือ ปัญหาหลักของ CORS misconfiguration ไม่ใช่แค่ “มีคนยิง request มาหา API” แต่คือ “เว็บที่ไม่ควรอ่านข้อมูล กลับอ่าน response ได้”
จุดเริ่มต้นของความเสี่ยง: อนุญาต origin กว้างเกินไป
ความผิดพลาดที่พบบ่อยคือ server ตอบกลับแบบกว้างเกินจำเป็น เช่น
Access-Control-Allow-Origin: *
หรือร้ายกว่านั้นคือสะท้อนค่า Origin ที่ผู้ร้องขอส่งมาโดยไม่ตรวจสอบจริง เช่น
- ผู้โจมตีส่ง
Origin: https://evil.example - server ตอบ
Access-Control-Allow-Origin: https://evil.example
พฤติกรรมแบบนี้แทบไม่ต่างจากการเปิดประตูให้ทุกเว็บไซต์เข้ามาอ่านข้อมูลได้ หาก endpoint นั้นมีข้อมูลผู้ใช้หรือข้อมูลภายใน ก็ถือว่าเสี่ยงมาก
อย่างไรก็ตาม การใช้ * ไม่ได้ผิดเสมอไป หากข้อมูลนั้นเป็น public จริง ๆ เช่น
- ไฟล์ config ที่เปิดเผยได้
- รูปภาพสาธารณะ
- endpoint ที่ไม่มีข้อมูลเฉพาะผู้ใช้
แต่ถ้าเป็น API ที่เกี่ยวข้องกับข้อมูลส่วนตัวหรือข้อมูลสำคัญ เช่น
/me/order-history/billing/admin/profile
ควรระวังเป็นพิเศษ เพราะ browser ของเหยื่ออาจแนบ cookie หรือ token ไปกับ request โดยอัตโนมัติ
คู่ผสมที่อันตราย: Credentials + Origin ที่ปล่อยหลวม
สถานการณ์ที่อันตรายที่สุดมักเกิดเมื่อมีสองสิ่งนี้พร้อมกัน
Access-Control-Allow-Credentials: true
และมีการอนุญาต origin แบบกว้าง หรือ validate แบบหลวมเกินไป
ผลคือ หากเหยื่อกำลัง login ค้างอยู่ และเข้าเว็บไซต์โจมตี JavaScript บนเว็บโจมตีอาจส่งคำขอไปยัง API ของเราได้ เบราว์เซอร์ก็จะแนบ cookie ให้ตามปกติ และถ้า CORS อนุญาต origin นั้น เว็บโจมตีก็สามารถอ่าน response กลับไปได้ทันที
นี่คือเส้นแบ่งสำคัญของความเสียหาย เช่น
- อ่านข้อมูล profile ได้
- อ่านประวัติคำสั่งซื้อได้
- อ่านข้อมูล billing ได้
- อ่านผลลัพธ์หลังเปลี่ยนค่าบางอย่างได้
- ในบางระบบอาจอ่าน token หรือข้อมูลแอดมินได้
เพราะฉะนั้น CORS ที่เปิดกว้างเมื่อใช้ร่วมกับ credentials จึงไม่ใช่แค่การตั้งค่าผิดเล็กน้อย แต่สามารถกลายเป็น incident ได้จริง
รูปแบบการตรวจ origin ที่พลาดบ่อย
ความผิดพลาดที่เจอบ่อยมากคือการตรวจสอบ origin แบบไม่ strict พอ เช่นตรวจแค่ว่ามีคำว่า example.com อยู่ในสตริงก็ถือว่าผ่าน
ตัวอย่างที่อันตราย ได้แก่
https://evil-example.comhttps://example.com.evil.comhttps://example.com.attacker.net
ทั้งหมดนี้อาจผ่านการตรวจแบบหลวม ๆ ได้ ทั้งที่ไม่ใช่โดเมนที่ควรอนุญาต
อีกตัวอย่างคือการใช้ regex ที่ไม่ anchor เช่น
/.*example.com.*/
แม้ดูเหมือนใช้งานได้ แต่จริง ๆ แล้วหลุดง่ายมาก เพราะมันยอมรับทุกค่า ที่มีข้อความ example.com แทรกอยู่ตรงไหนก็ได้
แนวทางที่ควรหลีกเลี่ยง ได้แก่
containsstartsWithแบบไม่รัดกุม- regex ที่ไม่ anchor ต้นและท้าย
- การ auto-allow ทุก subdomain โดยไม่มีเหตุผลทางธุรกิจชัดเจน
อย่ามองข้าม null origin
อีกกรณีหนึ่งที่อันตรายคือการเชื่อ null origin เช่น
Access-Control-Allow-Origin: null
หลายคนอาจคิดว่า null หมายถึงไม่มีที่มา จึงไม่น่ามีปัญหา แต่ในบริบทของ security มันมักแปลว่า “ต้องคิดต่อ” มากกว่า “ปลอดภัย”
null origin อาจเกิดจากหลายกรณี เช่น
- local file
- sandboxed iframe
- data URL
- execution context แปลก ๆ อื่น ๆ
หาก API สำคัญและยอม null ผู้โจมตีอาจพยายามสร้าง context พิเศษเพื่อให้ browser อ่านข้อมูลจาก API ได้
วิธีตรวจ CORS แบบ practical
การตรวจสอบเบื้องต้นสามารถทำได้ง่าย ๆ ด้วย DevTools หรือ curl โดยส่ง Origin แปลก ๆ เข้าไปแล้วดูว่า server ตอบอะไรกลับมา
ตัวอย่างแนวคิดในการทดสอบคือ
- ส่ง
Origin: https://attacker.test - ถ้า server สะท้อนค่านี้กลับมาใน
Access-Control-Allow-Origin - ต้องถามต่อทันทีว่า endpoint นี้มีข้อมูลสำคัญหรือไม่
- หากมี
Access-Control-Allow-Credentials: trueด้วย ให้ถือว่าเป็นสัญญาณอันตรายรุนแรง
payload ที่ควรลองทดสอบมีหลายรูปแบบ เช่น
https://evil.comhttps://example.com.evil.comhttps://evil-example.comnullhttp://trusted.comhttps://trusted.com:444
เหตุผลคือหลายระบบพลาดเรื่องเหล่านี้บ่อย
- scheme:
httpไม่เท่ากับhttps - port:
443ไม่เท่ากับ444 - subdomain:
trusted.comไม่เท่ากับapi.trusted.com
การเทียบ origin ที่ถูกต้องจึงต้องมองทั้ง scheme, host และ port ร่วมกัน
วิธีแก้ที่ถูกต้อง: ใช้ allowlist แบบ exact match
แนวทางที่ดีคือเก็บรายการ origin ที่อนุญาตจริงไว้ใน allowlist แล้วเทียบแบบ exact match เท่านั้น เช่น
https://app.example.comhttps://admin.example.com
หลักการสำคัญคือ
- อนุญาตเฉพาะ origin ที่จำเป็นต่อธุรกิจจริง ๆ
- ไม่ใช้ contains
- ไม่ใช้ startsWith แบบคร่าว ๆ
- ไม่เปิดทุก subdomain ถ้าไม่จำเป็น
- ถ้า response เปลี่ยนตาม origin ควรส่ง
Vary: Origin - เขียน automated test เพื่อกัน regression
หากเป็น private API ควรหลีกเลี่ยง Access-Control-Allow-Origin: * โดยเด็ดขาด โดยเฉพาะเมื่อ endpoint มีข้อมูลของผู้ใช้
กรณีหลาย tenant ต้องระวังมากเป็นพิเศษ
ระบบแบบ multi-tenant มักมีความเสี่ยงเพิ่มขึ้น หากเปิดให้ tenant ตั้งค่า CORS เองแล้วชี้ origin ไปยังโดเมนใดก็ได้โดยไม่ verify ownership
ปัญหาคือ tenant ที่ไม่หวังดีอาจกำหนด origin เป็นเว็บไซต์โจมตี และใช้ระบบของเราเป็นตัวช่วยในการอ่านข้อมูลข้ามขอบเขต
แนวทางที่ควรทำคือมีขั้นตอนพิสูจน์ความเป็นเจ้าของโดเมนก่อน เช่น
- ยืนยันผ่าน DNS record
- ยืนยันผ่านไฟล์บนเว็บ
- ใช้วิธี verify ownership แบบอื่นที่ตรวจสอบได้จริง
สิ่งนี้ช่วยลดความเสี่ยงจากการที่ผู้ใช้รายหนึ่งตั้งค่าระบบในแบบที่กระทบผู้ใช้รายอื่น
หลักคิดสั้น ๆ สำหรับการตั้งค่า CORS ให้ปลอดภัย
สรุปแนวทางที่ควรจำมีดังนี้
- public API ใช้
*ได้ ถ้าข้อมูลเป็น public จริง - private API ห้ามใช้
* Access-Control-Allow-Credentials: trueต้องใช้คู่กับ origin ที่ล็อกแน่นเท่านั้น- อย่าตรวจ origin แบบหลวม ๆ
- ระวัง
null origin - ส่ง
Vary: Originเมื่อ response เปลี่ยนตาม origin - เขียน test เพื่อป้องกันการตั้งค่าถอยหลังโดยไม่ตั้งใจ
สรุป
CORS ที่ดีไม่ใช่การเปิดให้ frontend ทำงานง่ายที่สุด แต่คือการเปิดเท่าที่จำเป็นต่อธุรกิจจริง ๆ เท่านั้น ยิ่งอนุญาต origin กว้างมากเท่าไร ก็ยิ่งต้องมั่นใจมากขึ้นว่าไม่มีข้อมูลที่ไม่ควรถูกอ่านอยู่เบื้องหลัง
สุดท้ายแล้ว browser จะทำตามกติกาที่ server ประกาศอย่างซื่อสัตย์เสมอ ถ้าเราประกาศผิด มันก็อาจช่วยให้ผู้โจมตีอ่านข้อมูลได้อย่างถูกต้องตามกติกาที่เราตั้งไว้เอง