นักพัฒนาไม่เข้าใจ CORS (2019)
(fosterelli.co)- ช่องโหว่ของเว็บเซิร์ฟเวอร์โลคัลของ Zoom แสดงให้เห็นว่าเมื่อเว็บดีเวลอปเปอร์จำนวนมากเข้าใจ วิธีการทำงานของ CORS ผิดพลาด ขอบเขตความปลอดภัยก็อาจพังทลายลงได้ง่าย
- Zoom สื่อสารกับเซิร์ฟเวอร์โลคัล
localhost:19421และใช้ ขนาดของรูปภาพ เพื่อส่งรหัสสถานะแทน AJAX ซึ่งตีความได้ว่าเป็นการทำทางอ้อมเพื่อหลบเลี่ยง CORS - Chrome ใช้ CORS header กับเว็บเซิร์ฟเวอร์บน localhost ด้วย และการสื่อสารระหว่างฟรอนต์เอนด์กับแบ็กเอนด์ที่อยู่คนละพอร์ตบน localhost ก็ได้รับการรองรับในเบราว์เซอร์
- การออกแบบที่ปลอดภัยกว่าคือให้เซิร์ฟเวอร์โลคัลมี REST API และตั้งค่า
Access-Control-Allow-Originเพื่อจำกัดให้เฉพาะ JavaScript ของ zoom.us เข้าถึงได้ - การหลบเลี่ยง same-origin policy อาจทำให้โค้ดทำงานได้ แต่ฟังก์ชันที่มีสิทธิ์ของเซิร์ฟเวอร์โลคัลอาจถูกเปิดเผยให้ทุกเว็บไซต์บนอินเทอร์เน็ตเข้าถึงได้
การหลบเลี่ยง CORS ที่เกิดจากเว็บเซิร์ฟเวอร์โลคัลของ Zoom
- จากการทำงานที่ปรึกษาแบบฟูลสแต็กกับนักพัฒนาหลายขนาดหลายอุตสาหกรรม พบซ้ำๆ ว่าเว็บดีเวลอปเปอร์จำนวนมากไม่เข้าใจ CORS
- ใน ช่องโหว่ล่าสุดของ Zoom นักวิจัยความปลอดภัย Jonathan Leitschuh พบว่า Zoom เปิดเว็บเซิร์ฟเวอร์
http://localhost:19421ไว้บนเครื่องผู้ใช้- เมื่อผู้ใช้เปิดลิงก์ Zoom เว็บไซต์ Zoom จะส่งคำขอไปยังเว็บเซิร์ฟเวอร์บน localhost เพื่อเรียกแอป Zoom แบบเนทีฟขึ้นมา
- แทนที่จะใช้คำขอ AJAX ปกติ ระบบกลับโหลดรูปภาพจากเว็บเซิร์ฟเวอร์โลคัลของ Zoom และใช้ขนาดรูปที่ต่างกันเพื่อสื่อถึงข้อผิดพลาดและรหัสสถานะของเซิร์ฟเวอร์
- การตีความว่าบราว์เซอร์เพิกเฉยนโยบาย CORS ของเซิร์ฟเวอร์ localhost นั้นไม่ถูกต้อง และ Chrome ก็ เคารพ CORS header ของเว็บเซิร์ฟเวอร์บน localhost
- แม้ตอนรันฟรอนต์เอนด์ของ Create React App และแบ็กเอนด์ API คนละพอร์ตบน localhost ก็ยังเกิดคำขอข้ามต้นทาง และเบราว์เซอร์ทั้งหมดรองรับกรณีนี้
- ดูเหมือนว่าเมื่อคำขอ AJAX ถูกบล็อก Zoom จึงใช้เทคนิค image hack เพื่อหลบเลี่ยง CORS
- ผลคือไม่ใช่แค่เว็บไซต์ Zoom เท่านั้น แต่เว็บไซต์อื่นๆ บนอินเทอร์เน็ตก็สามารถทริกเกอร์การทำงานของไคลเอนต์เนทีฟและเข้าถึงการตอบสนองได้ด้วย
ทางเลือกที่ปลอดภัยกว่าและปัญหา UX ที่ยังคงอยู่
- แนวทางที่ปลอดภัยคือให้เว็บเซิร์ฟเวอร์บน
localhost:19421ทำงานเป็น REST API และตั้งค่า headerAccess-Control-Allow-Originเป็นhttps://zoom.us- แบบนี้จะมีเพียง JavaScript ที่รันอยู่บนโดเมน zoom.us เท่านั้นที่สื่อสารกับเว็บเซิร์ฟเวอร์บน localhost ได้
- zoom.us ยังสามารถใส่ header Content Security Policy เพื่อบล็อกการเรนเดอร์ iframe และป้องกันไม่ให้ Zoom meeting เปิดขึ้นมาอัตโนมัติในเบื้องหลังได้
- ปัญหาที่หน้าเว็บใดๆ ก็ยังสามารถรีไดเรกต์เบราว์เซอร์ไปยังลิงก์ประชุมของ zoom.us ได้ ยังคงมีอยู่
- อย่างไรก็ตาม นี่ใกล้เคียงกับ ประสบการณ์ผู้ใช้ ที่ Zoom เลือกมากกว่าจะเป็นช่องโหว่ซอฟต์แวร์
- Zoom กำลังทำลายความคาดหวังของผู้ใช้ที่คิดว่าเมื่อคลิกลิงก์ กล้องและไมโครโฟนจะไม่ถูกเปิดให้คนแปลกหน้าโดยกะทันหัน
- หากต้องการหลีกเลี่ยงป๊อปอัปของเบราว์เซอร์ด้วยเหตุผลด้าน UX ก็สามารถแสดงป๊อปอัปภายในแอปได้ และ Google Meet ก็ใช้วิธีนี้ได้ดี
- การรันเว็บเซิร์ฟเวอร์บน localhost เองก็เป็นความพยายามที่เสี่ยงอยู่แล้ว และโดยเฉพาะอย่างยิ่งไม่ควรเปิดฟังก์ชันที่มีสิทธิ์ เช่น การติดตั้งซอฟต์แวร์ ให้ทุกเว็บไซต์บนอินเทอร์เน็ตใช้งานได้
- CORS ถูกสร้างมาเพื่อจัดการสถานการณ์แบบนี้อย่างปลอดภัย จึงไม่ควรถูกหลบเลี่ยง
ความสับสนเรื่อง CORS ไม่ใช่ความผิดพลาดของ Zoom เจ้าเดียว
- ยังไม่แน่ชัดว่า Zoom เลือกวิธีนี้เพราะไม่เข้าใจ CORS จริงหรือไม่
- lerunicorn บน Reddit มองว่า Firefox อาจบล็อก XHR จากต้นทางที่ปลอดภัยไปยังต้นทางที่ไม่ปลอดภัย
- แต่ Firefox รองรับกรณีนี้เมื่อ origin เป็น localhost
- แอปเนทีฟสามารถสร้างใบรับรองแบบ self-signed ของตัวเองได้ และยังใช้ ส่วนขยายเบราว์เซอร์ ได้ด้วย
- ไม่ว่าในกรณีใดก็ไม่ใช่เหตุผลอันชอบธรรมที่จะละเว้น การกรองต้นทาง
- ความสับสนเรื่อง CORS ไม่ได้เกิดกับ Zoom เท่านั้น
- บน Stack Overflow มีคำถามเกี่ยวกับ
Access-Control-Allow-Originจำนวนมาก - ในตัวอย่างของ Express ก็มีหน้าที่แนะนำ ค่าเริ่มต้นที่ไม่ปลอดภัย ซึ่งหากคัดลอกไปใช้ตรงๆ อาจทำให้แอปพลิเคชันมีช่องโหว่ได้
- เวนเดอร์รายอื่นก็เคยเจอ ช่องโหว่แบบเดียวกับ Zoom มาก่อน
- บน Stack Overflow มีคำถามเกี่ยวกับ
- นักพัฒนาต้องการให้โค้ดทำงานได้ แต่หากหลบเลี่ยง same-origin policy ทั้งหมด ก็จะทำให้สิทธิ์บนเครื่องโลคัลถูกเปิดเผยให้เว็บไซต์ภายนอกเหมือนกรณีของ Zoom
- ความสับสนเรื่อง CORS พบได้ทั้งในนักพัฒนาที่มีประสบการณ์และมือใหม่ ยังไม่ชัดเจนว่าเป็นเพราะ CORS API ซับซ้อนเกินไปหรือเพราะการสอนเรื่อง CORS และ CSP ยังไม่เพียงพอ แต่แนวทางปัจจุบันก็ทำงานได้ไม่ดีนัก
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ดูเหมือนว่าแม้แต่บทความต้นทางก็ยังไม่เข้าใจ CORS อย่างถูกต้อง หรืออธิบายผิดอย่างมาก
Access-Control-Allow-Origin: https://zoom.usไม่ได้ทำให้มั่นใจว่าเฉพาะ JavaScript จากโดเมน zoom.us เท่านั้นที่จะสื่อสารกับเซิร์ฟเวอร์ localhost ได้ JavaScript จากเว็บไซต์อื่นก็ยังส่งคำขอไปที่localhost:19421ได้เหมือนกัน CORS ไม่ได้มีไว้เพื่อจำกัดอะไร แต่เป็นกลไกที่ใช้ผ่อนคลายข้อจำกัดที่มีอยู่เดิม ส่วน header นี้เพียงแค่ทำให้ JavaScript ที่รันอยู่บน zoom.us อ่านการตอบกลับจากlocalhost:19421ได้เท่านั้น และเนื่องจากตัวคำขอจะเกิดขึ้นอยู่แล้ว ฝั่งแบ็กเอนด์จึงต้องออกแบบไม่ให้เกิดผลข้างเคียงคำขอ GET นั้นถูกส่งออกไปได้จริง แต่ตามหลักแล้วควรเป็นแบบ idempotent ดังนั้นถ้าเซิร์ฟเวอร์อิมพลีเมนต์ ถูกต้องก็ไม่ควรทำให้เกิดผลข้างเคียง และสำหรับ GET ประเด็นสำคัญคืออ่านการตอบกลับได้หรือไม่ ในทางกลับกัน คำขอแบบ non-idempotent ที่อาจมีผลข้างเคียง ในสถานการณ์ข้ามแหล่งที่มาจะมีการส่งคำขอ preflight OPTIONS ก่อนแทนคำขอจริง และถ้าการตอบกลับของ OPTIONS ไม่มี header ที่ถูกต้อง คำขอจริงจะไม่ถูกส่ง
ความเข้าใจผิดเกี่ยวกับ CORS แพร่หลายมาก และเอกสารก็มักขัดแย้งกันเอง จึงยากที่จะคาดหวังว่าคู่สนทนาที่เราไม่รู้จักจะติดตั้งใช้งานได้ถูกต้อง ถ้าโปรโตคอลหนึ่งก่อให้เกิดความสับสนอย่างกว้างขวางขนาดนี้ ต่อให้ฝั่งหนึ่งทำงานถูกต้อง ก็ไม่อาจรู้ได้ว่าอีกฝั่งจะถูกต้องด้วยหรือไม่ ถ้าผู้คนแก้โค้ดไปเรื่อย ๆ จนมันทำงานกับ implementation ของอีกฝ่ายได้ ก็ยิ่งไม่ชัดว่าฝั่งตัวเองผิดหรืออีกฝ่ายผิดกันแน่
ตัวอย่างเช่น POST ที่มี
Content-Typeเป็นtext/jsonจะส่งไปยังโฮสต์บุคคลที่สามโดยไม่มี OPTIONS preflight ไม่ได้ แต่ POST ที่เป็นmultipart/form-dataนั้นอนุญาต และ CORS จะไม่บล็อก แล้วถ้า endpoint ไม่ตรวจสอบContent-Typeอย่างเข้มงวดและสมมติว่าเป็น JSON ไปเลย ก็เท่ากับว่าเว็บไซต์ใดก็ได้สามารถส่ง POST ได้โดยที่ผู้ใช้ไม่ต้องโต้ตอบอะไรนักพัฒนาเว็บที่ดีไม่ควรทำให้ GET/HEAD/OPTIONS เปลี่ยนสถานะ และการเข้าร่วมประชุมก็เป็นการเปลี่ยนสถานะ ส่วน PUT/DELETE ก็ควรเป็น idempotent เช่นกัน API แบบ POST ที่ไม่ใช่ JSON หรือฟอร์มควรตรวจสอบ header
Content-Typeและ POST ที่ใช้PUT/PATCH/DELETEหรือContent-Typeที่ไม่ใช่รูปแบบฟอร์ม จะกระตุ้น preflight ทำให้ CORS ถูกตรวจสอบก่อนที่คำขอจริงจะไปถึงเซิร์ฟเวอร์แค่สร้างใบรับรองขึ้นมาอย่างเดียวใช้ไม่ได้ มันต้องถูกติดตั้งเป็นใบรับรอง root CA ลงใน trust store ของเบราว์เซอร์ทั้งหมดบนเครื่องนั้นด้วย ถ้า private key ของ root CA ไม่ได้รับการปกป้องอย่างเหมาะสม เว็บไซต์ใด ๆ ก็สามารถทำ man-in-the-middle attack ได้ ดังนั้นอย่างน้อยควรมี name constraints (https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10) แต่ใน Chrome ก่อน v112 ปี 2023 สิ่งนี้ใช้กับ root CA ไม่ได้ (https://alexsci.com/blog/name-non-constraint/) จึงต้องเพิ่ม intermediate CA แล้วไปใส่ข้อจำกัดไว้ตรงนั้น แน่นอนว่าควรทิ้งคีย์ root CA ไปเลย
ก่อนหน้านี้ผมเคยเพิ่ม basic constraints ในโปรเจกต์ที่ใช้ local root CA แต่ใส่ผิดไว้ที่ root CA และก็ไม่ได้ทดสอบกับทุกเบราว์เซอร์ด้วย
อยากให้มีคนอ่าน เอกสาร CORS ของ MDN มากกว่านี้ มันช่วยผมได้มากตอนพยายามทำความเข้าใจ CORS และพอมาเห็นคอมเมนต์ที่นี่ก็เพิ่งรู้ว่าผู้คนรู้สึกว่ายากกันขนาดนี้
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
สิ่งที่เข้าใจยากไม่ใช่แค่ CORS แต่คือนักพัฒนาจำนวนมากไม่เข้าใจ threat model อย่างแท้จริง
ต่อให้ฟังคำอธิบายแล้ว หลายครั้งก็ยังนึกภาพไม่ออกว่าทำไมมันถึงเป็นปัญหาใหญ่ โดยเฉพาะเมื่อแบ็กเอนด์เป็นฝ่ายต้องตั้งค่า CORS บ่อย ๆ แต่ CORS ไม่ใช่กลไกปกป้องสิทธิ์การเข้าถึง ดังนั้นจากมุมมองของแบ็กเอนด์มันเลยดูไม่สำคัญนัก ผู้โจมตีเหมือนจะเอาอะไรไปไม่ได้ ขณะที่ฝั่งฟรอนต์เอนด์ก็มักมองว่าเป็นอุปสรรคที่น่ารำคาญ บทความนี้แสดงตัวอย่างที่เป็นรูปธรรมได้ดี
ในฐานะผู้ดูแลระบบ ผมจึงไปแก้ให้ถูกต้องอีกครั้งที่ load balancer และอย่างน้อยตอนนี้แอปพลิเคชันก็ใช้งานได้แล้ว CORS เข้าใจยากก็จริง แต่ที่น่าเสียดายกว่าคือมีนักพัฒนาจำนวนมากที่ไม่เข้าใจทั้ง threat model ที่ CORS พยายามป้องกัน และการพัฒนาเว็บโดยรวม โดยเฉพาะ HTTP protocol
multipart/form-dataใช้ได้ แต่ JavaScript ของแอปพลิเคชันกลับไม่ได้CORS เป็นสิ่งที่เลือกใช้ได้ และไลบรารีหรือเครื่องมืออื่นอาจเมินมันไปเลยก็ได้ CORS มีความหมายจริง ๆ แค่กับการป้องกัน XSS และ CSRF ต่อผู้ใช้จริงที่ล็อกอินอยู่ ส่วนสถานการณ์โจมตีแบบอื่นก็ไม่มีความหมาย เพราะยังไงก็ใช้สคริปต์หรือโปรแกรมที่ปลอม HTTP header ได้อยู่แล้ว สุดท้ายผู้คนจึงเปิดตัวเลือก CORS ทั้งหมดทิ้งไว้ ซึ่งเป็นกรณีเลวร้ายที่สุดเพราะเท่ากับยอมให้เกิด XSS และ CSRF
อ่านคอมเมนต์แล้วรู้สึกว่าระดับข้อมูลในนี้ต่ำมาก และยิ่งพิสูจน์ประเด็นของผู้เขียนตรงๆ
ถ้าเคยพัฒนาเว็บมาก่อนที่ CORS จะเกิดขึ้น ก็จะเข้าใจว่าเดิมที cross-domain requests ถูกห้ามอยู่แล้ว และ CORS เกิดขึ้นมาเพื่อผ่อนข้อจำกัดด้านความปลอดภัยนี้ ดังนั้นจึงรับได้ง่ายว่าถ้าอยากให้ทำงานที่ต้องการ ก็แค่เปิดใช้ CORS
ในทางกลับกัน คนที่เริ่มเรียนพัฒนาเว็บหลังยุค CORS จะเห็นแค่ลำดับว่า ลองทำ cross-origin request, เบราว์เซอร์ตัดสินว่าไม่อนุญาต, ลองทำ CORS preflight, แล้วพอล้มเหลวก็มี CORS error ขึ้นในคอนโซล ถ้าไม่รู้การทำงานภายในและไม่ได้อ่านเอกสาร ก็มักเดาว่า CORS คือสาเหตุที่บล็อกคำขอ แล้วพยายาม “ปิดการทำงานของ CORS” แต่จริงๆ แล้ว CORS ไม่ใช่ต้นเหตุของปัญหา มันคือทางแก้
พอมีคนที่เข้าใจผิดแบบเดียวกันพูดซ้ำอย่างมั่นใจใน tutorial และการถกเถียงออนไลน์ ก็ยิ่งทำให้งงมากขึ้น
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
พออ่านคอมเมนต์ก็ยืนยันได้ว่าไม่ได้มีแค่ฉันที่เป็นแบบนี้ เหตุผลที่ไม่มีใครเข้าใจ CORS จริงๆ คือ มันซับซ้อนเกินไปและมีจุดขัดกันเยอะมาก
ทั้งมาตรฐานและเฮดเดอร์ก็เปลี่ยนตลอด นักพัฒนาส่วนใหญ่เลยมักลองปรับโน่นนี่ไปเรื่อยๆ จนกว่าจะใช้งานได้ แล้วก็ปล่อยโปรดักต์ออกไปจบ แม้จะทำงานได้ แต่ใน developer console อาจยังมี error กับ warning อยู่ ทว่าถ้าภายนอกดูเหมือนทำงานปกติดี ก็มักจะไม่ไปแตะมันอีก
ถ้าอยากเข้าใจ CORS ต้องเข้าใจ same-origin policy ก่อน
โดยเฉพาะถ้าคำถามว่า “ทำไมสิ่งนี้ถึงจำเป็น?” ยังตอบยาก จุดเริ่มต้นที่ดีคือ: https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy
เมื่อก่อนเคยใช้ same-origin policy เป็นคำถามสัมภาษณ์ แต่ผู้สมัครจำนวนมากไม่คุ้นเคยกับมัน เลยทำให้คำถามนี้ให้ข้อมูลได้ไม่มากนัก
เพราะถ้าเคยพัฒนาเว็บแอปมา ยังไงสักวันก็ต้องเคยเจอ same-origin policy ถ้าไม่รู้จัก ก็มักต้องถามต่อว่าแล้วสื่อสารกับ backend อย่างไร เป็นต้น เคยเจอปัญหา CORS แต่เลือกใช้แค่ทางลัดที่เร็วที่สุดแล้วลืมมันไป หรือเคยพยายามทำความเข้าใจจริงๆ ไหม เรื่องนี้เป็นสัญญาณที่มีประโยชน์สำหรับบางบทบาท
แต่สำหรับตำแหน่ง backend จะเหมาะน้อยกว่า เพราะไม่ใช่นักพัฒนา backend ทุกคนจะได้ทำงานใกล้ชิดกับทีม frontend ที่ต้องเจอปัญหา CORS บ่อยๆ
สิ่งที่จำได้เกี่ยวกับ CORS คือการดีบักใช้เวลานานกว่าที่คิดมาก ข้อความผิดพลาดจากเบราว์เซอร์ก็ตั้งใจทำให้ข้อมูลน้อย และ CORS error ก็แยกจากรูปแบบความล้มเหลวอื่นได้ยากในตอนแรก
แน่นอนว่าถ้าเซิร์ฟเวอร์ไม่เข้าใจ CORS request แล้วคืน response แปลกๆ มา สุดท้ายมันก็อาจถูกตีความเป็น CORS failure ได้
อ่านคอมเมนต์แล้วค่อนข้างสนุก เลยขอเสริมว่า same-origin policy ป้องกันไม่ให้เบราว์เซอร์รั่วไหลข้อมูลไปยังเว็บไซต์ที่ไม่มีสิทธิ์เข้าถึง และ CORS ทำให้สามารถลดความเข้มของการป้องกันนั้นได้
ตัวอย่างเช่น same-origin policy จะกันไม่ให้
example.comดึงรายการสมัครรับข้อมูลของyoutube.comมาได้ แต่ถ้าใช้ CORS ก็อาจอนุญาตให้example.comเข้าถึงyoutube.com/public/*ได้อีกการใช้งานหนึ่งคือช่วยป้องกันไม่ให้ backend API ถูกเรียกภายใต้ frontend อื่นจนกลายเป็นการขโมยข้อมูล เช่น กรณีที่ผู้ใช้ล็อกอินกับบริการจริงอยู่ แต่กำลังอยู่บน
g00gle.comและทุกคำขออาจถูกโจมตีแบบ man-in-the-middle ได้ฉันก็เป็นหนึ่งในคนแบบนั้นเหมือนกัน CORS เป็นหัวข้อที่ต้องกลับมาเรียนใหม่เป็นระยะ และก็ลืมอยู่เรื่อย เลยไม่ค่อยอยู่ในหัว
น่าจะเพราะเป็น backend developer เลยแทบไม่ค่อยเจอปัญหา CORS ของที่ไม่ได้ใช้ทุกวันก็มักลืมได้ง่าย
ในโลกปกติ ข้อความผิดพลาดควรมีคำใบ้อย่าง “response header” หรือ “meta tag” อยู่ด้วย แต่ดูเหมือนบริษัทผู้พัฒนาเบราว์เซอร์รายใหญ่จะจ้างคนที่ชอบเขียนข้อความลึกลับมาใช้ Chrome คำว่า “requested resource” ยังถือว่าดีกว่าเจ้าอื่น แต่ก็ยังเหมือนรหัสลับอยู่ดี
ข้อความที่ดีกว่าควรเป็นประมาณว่า resource จาก
https://bank.comไม่อนุญาตคำขอข้าม origin เพราะไม่มี CORS header หรือ origin ปัจจุบันไม่ได้อยู่ในรายการที่ CORS อนุญาต และควรแสดง preflight request ใน network tab พร้อมลิงก์ไป MDN ด้วย ส่วน CSP ก็ควรบอกว่าดึง resource ไม่ได้เพราะ CSP header ของหน้านี้ และเชื่อมไปยัง page request header หรือ meta tag ใน inspector ด้วยจะดีกว่าสุดท้ายแล้วมักอาศัยสมมติฐานว่าเซิร์ฟเวอร์จะถูกเข้าถึงผ่านคำขอจากเบราว์เซอร์ที่ไม่ถูกดัดแปลงเท่านั้น ช่องโหว่ของ Zoom ก็เกิดขึ้นเพราะฝั่งไคลเอนต์สามารถหลบเลี่ยง CORS และ CSP ได้ง่ายเกินไป และแม้ Zoom จะผิด แย่ และขี้เกียจจริง แต่ก็รู้สึกว่าชุมชนที่ยังคงยึดโมเดลแบบนี้ต่อไปก็มีส่วนรับผิดชอบเช่นกัน
ฉันเข้าใจว่า same-origin policy ป้องกันไม่ให้เบราว์เซอร์รันสคริปต์อันตรายแล้วขโมยข้อมูลออกไปได้อย่างไร และก็เข้าใจด้วยว่าเซิร์ฟเวอร์สามารถประกาศความไว้วางใจต่อ origin เพิ่มเติมเพื่อผ่อนคลาย SOP ได้ผ่านเฮดเดอร์
Access-Control-Allow-Originถึงอย่างนั้นก็ยังไม่เข้าใจจุดประสงค์ของเฮดเดอร์
Access-Control-Allow-Headersมันไม่ได้ดูเหมือนช่วยเพิ่มความปลอดภัยของเบราว์เซอร์ และยิ่งไม่ใช่ความปลอดภัยของเซิร์ฟเวอร์ เลยสงสัยว่านักออกแบบโปรโตคอลใส่มาเพื่อ “ความครบถ้วนสมบูรณ์” หรือเปล่า เกี่ยวข้องกับ: https://stackoverflow.com/questions/17992042