- เพื่อป้องกัน การโจมตี XSS ซึ่งเป็นช่องโหว่สำคัญของเว็บ Firefox จึงรองรับ Sanitizer API ที่เป็นมาตรฐาน เป็นรายแรก
- หากใช้เมธอด setHTML() แทน innerHTML แบบเดิม ระบบจะทำ การ sanitize HTML ที่ไม่น่าเชื่อถือโดยอัตโนมัติก่อนแทรกลง DOM เพื่อลบสคริปต์อันตราย
- นักพัฒนาสามารถควบคุมองค์ประกอบและแอตทริบิวต์ที่อนุญาตได้ผ่าน การตั้งค่าแบบกำหนดเอง หากค่าตั้งต้นเข้มงวดเกินไปหรือไม่เพียงพอ
- ฟีเจอร์นี้ของ Firefox เมื่อใช้งานร่วมกับ Trusted Types จะช่วยยกระดับความปลอดภัยของเว็บโดยรวม และทำให้นักพัฒนาป้องกัน XSS ได้แม้ไม่มีทีมความปลอดภัยเฉพาะทาง
ช่องโหว่ XSS และแนวทางรับมือของ Firefox
- Cross-site scripting (XSS) เกิดขึ้นเมื่อผู้โจมตีสามารถแทรก HTML หรือ JavaScript ตามอำเภอใจผ่านเนื้อหาที่ผู้ใช้ป้อนเข้ามา
- ผู้โจมตีอาจใช้สิ่งนี้เพื่อเฝ้าดูการโต้ตอบของผู้ใช้หรือขโมยข้อมูล
- XSS ถูกจัดอยู่ใน 3 อันดับแรกของช่องโหว่เว็บ (CWE-79) มาเกือบ 10 ปี
- Firefox ขับเคลื่อนมาตรฐาน Content-Security-Policy (CSP) มาตั้งแต่ปี 2009 เพื่อเสริมการป้องกัน XSS
- CSP จำกัดทรัพยากรที่เว็บไซต์สามารถโหลดและรันได้
- อย่างไรก็ตาม เนื่องจากต้องปรับโครงสร้างเว็บไซต์เดิมและต้องมีการทบทวนความปลอดภัยอย่างต่อเนื่อง จึงมี ข้อจำกัดในการนำไปใช้ในวงกว้าง
บทบาทของ Sanitizer API และ setHTML()
- Sanitizer API มอบวิธีมาตรฐานสำหรับแปลง HTML อันตรายให้เป็นรูปแบบที่ปลอดภัย
- ในโค้ดตัวอย่าง องค์ประกอบ
<img src="x" onclick="alert('XSS')"> จะถูกลบออก และจะเหลือเพียง <h1>Hello my name is</h1>
- เมธอด setHTML() จะทำกระบวนการ sanitize โดยอัตโนมัติขณะทำการแทรก HTML จึงรับประกัน พฤติกรรมที่ปลอดภัยเป็นค่าเริ่มต้น
- เพียงแค่แทนที่การกำหนดค่า
innerHTML เดิมด้วย setHTML() ก็สามารถสร้าง การป้องกัน XSS ที่แข็งแกร่ง ได้
- หาก ค่าตั้งต้น เข้มงวดเกินไปหรือหย่อนเกินไป นักพัฒนาสามารถกำหนดองค์ประกอบ HTML และแอตทริบิวต์ที่อนุญาตได้ผ่าน การตั้งค่าแบบกำหนดเอง
- สามารถใช้เครื่องมือ Sanitizer API playground เพื่อทดลองได้
การทำงานร่วมกับ Trusted Types
- Trusted Types API ช่วยควบคุมการ parse และการแทรก HTML จากส่วนกลาง เพื่อเพิ่มชั้นความปลอดภัยอีกระดับ
- เมื่อใช้
setHTML() ก็สามารถบังคับใช้นโยบาย Trusted Types ได้อย่างง่ายดาย
- นโยบายที่เข้มงวดสามารถอนุญาตเฉพาะ
setHTML() และบล็อกวิธีแทรกที่เสี่ยงอื่น ๆ เพื่อช่วย ป้องกันการถดถอยของ XSS ในอนาคต
ผลด้านความปลอดภัยที่ดีขึ้นใน Firefox 148
- Firefox 148 รองรับทั้ง Sanitizer API และ Trusted Types ทำให้ ระดับความปลอดภัยพื้นฐานสูงขึ้นอย่างมาก
- นักพัฒนาสามารถ ป้องกัน XSS ได้ด้วยการเปลี่ยนโค้ดเพียงเล็กน้อย โดยไม่ต้องมีนโยบายความปลอดภัยที่ซับซ้อนหรือทีมความปลอดภัยแยกต่างหาก
- คาดว่าการนำมาตรฐานนี้มาใช้จะช่วย ขยายสภาพแวดล้อมเว็บที่ปลอดภัย ไปยังทุกเบราว์เซอร์
สรุป
- Firefox 148 รองรับการให้เว็บดีเวลอปเปอร์ บล็อกการโจมตี XSS ได้ง่ายขึ้น ผ่าน เมธอด setHTML() และ Sanitizer API
- ฟีเจอร์นี้ช่วยอุดข้อจำกัดของ CSP และเป็นจุดเปลี่ยนในการผลักดัน วิธีแทรก HTML ที่ปลอดภัยเป็นค่าเริ่มต้น ให้กลายเป็นมาตรฐานเว็บ
- เมื่อทำงานร่วมกับ Trusted Types ก็จะช่วยให้รักษาความปลอดภัยระยะยาวและ ป้องกันการถดถอยของ XSS ได้
- โดยสรุป Firefox กำลังเป็นผู้นำการเปลี่ยนผ่านสู่ สภาพแวดล้อมเว็บที่มีความปลอดภัยเป็นค่าเริ่มต้น
2 ความคิดเห็น
โอ้ แบบนี้จำเป็นจริง ๆ นะครับ ถ้ารองรับได้ในทุกเบราว์เซอร์ก็น่าจะดีมากเลย
ความคิดเห็นจาก Hacker News
ฟีเจอร์แบบนี้ทำให้รู้สึก ไม่ค่อยสบายใจ อยู่เสมอ
เพราะมีทั้งเมธอดที่ปลอดภัยแม้จะส่งอินพุตจากผู้ใช้เข้าไปตามตรง และเมธอดที่ไม่ปลอดภัยปะปนกันอยู่ แต่ดูจากชื่ออย่างเดียวแยกได้ยาก
ตามอุดมคติแล้ว ฟังก์ชันที่อันตรายควรมีชื่อที่บอกความเสี่ยงให้ชัดเจนตั้งแต่แรก
อีกอย่าง แนวคิดเรื่องการ “sanitize” HTML เองก็คลุมเครือ และตัดสินได้ยากว่าแท้จริงแล้วปลอดภัยหรือไม่
คือการลบองค์ประกอบหรือแอตทริบิวต์ที่สามารถรันสคริปต์ได้ และตรรกะนี้ทำงานอยู่ภายในเอนจินของเบราว์เซอร์ จึงจัดการได้แม่นยำกว่าพวก sanitizer แบบอิงสตริง
ดูรายละเอียดได้ใน เอกสาร MDN ของ setHTML
elementNode.textContentปลอดภัยแม้ใช้กับอินพุตที่ไม่น่าเชื่อถือ แต่elementNode.innerHTMLไม่ใช่ตัวแรกจะ escape ทุกอักขระ ส่วนตัวหลังไม่ escape อะไรเลย
ยังมีความเห็นด้วยว่า “HTML sanitization” เป็นปัญหาที่แก้ให้สมบูรณ์ไม่ได้โดยพื้นฐาน
ดูการถกเถียงที่เกี่ยวข้องได้ใน คอมเมนต์นี้
API แบบนี้ไม่ควรผ่านตั้งแต่ขั้นตอนเสนอด้วยซ้ำ
innerHTMLปนกับsetHTMLแต่ควรถอดinnerHTMLออกไปเลย และถ้าต้องการพฤติกรรมแบบเดิมก็ค่อยใช้setHTMLUnsafeinnerHTMLได้ด้วยการตั้งค่าระดับ global ก็น่าจะดีแต่ถ้าทำแบบนั้น เว็บไซต์อาจใช้งานไม่ได้บนเบราว์เซอร์รุ่นเก่า
Content-Security-Policy: require-trusted-types-for 'script'ก็จะบล็อกการส่งสตริงธรรมดาเข้าเมธอดที่ไม่มี sanitizer ได้ถ้าตามตัวอย่าง ผู้ใช้สามารถแทรกแท็กอย่าง
<h1>หรือ<br>เข้าไปในชื่อผู้ใช้ได้ ต่อให้บล็อกการรันสคริปต์ไว้ ก็ยังคงเกิด การฉีดมาร์กอัปตามอำเภอใจ ได้อยู่ดียังอาจใช้แท็ก
<style>ไปเปลี่ยน CSS ได้ด้วย เช่น เปลี่ยนหน้าตาของหน้าโปรไฟล์ PayPalก็เลยอดสงสัยไม่ได้ว่ามีใครต้องการแบบนี้จริงหรือ
สามารถเพิ่มชั้นป้องกันด้วยการนำ HTML ที่สร้างจาก Markdown ไปจำกัดอีกครั้งด้วย sanitizer เพื่ออนุญาตเฉพาะบางแท็ก
innerTextหรือtextContentแทนinnerHTMLsetHTMLมีไว้ใช้แทนinnerHTMLsetHTML()เข้มงวดเกินไปหรือหลวมเกินไป นักพัฒนาก็สามารถให้ การตั้งค่าแบบกำหนดเอง เพื่อระบุองค์ประกอบ HTML และแอตทริบิวต์ที่อนุญาตได้โดยตรงดูการถกเถียงที่เกี่ยวข้องได้ในเธรดนี้
สุดท้ายแล้วฝั่งแบ็กเอนด์ก็ยังคงต้อง sanitize ชื่อผู้ใช้ด้วยวิธีมาตรฐาน และตอนแสดงผลก็ต้องทำ HTML escape
ตาม RFC 2119 นี่เป็นข้อกำหนดระดับ “SHOULD”
ยินดีที่เห็นฟีเจอร์นี้ออกมา แต่กว่าที่ การรองรับของเบราว์เซอร์ จะครอบคลุมเพียงพอคงต้องใช้เวลา
ตรวจสอบสถานะการรองรับได้ที่ Can I use
ระหว่างนั้นก็ใช้ polyfill แทนได้
พาดหัวค่อนข้างชวนให้เข้าใจเกินจริงนิดหน่อย
จริง ๆ แล้วก็น่าจะทำ sanitization ได้ด้วยฟังก์ชันตรวจสอบอินพุตก่อนส่งให้
innerHTMLไม่ใช่หรือเพียงแต่ความพยายามแบบนี้สุดท้ายก็ดูเหมือน การประดิษฐ์ล้อใหม่
อีกทั้งบน Firefox รุ่นเก่า hacks.mozilla.org ยังเปิดไม่ได้เลย และบน Pale Moon หรือ SeaMonkey นั้น MDN ก็แสดงผลเพี้ยน
เหมือนกับว่า “คาร์เทลเบราว์เซอร์” กำลังทำลายเว็บ
และยังเป็นข้ออ้างที่ไม่ได้คำนึงถึงปัญหา parser differential ด้วย
ถ้าใช้ Sanitizer API ผิดวิธี มันอาจกลายเป็น footgun ได้
โดยเฉพาะเวลาที่ใช้โหมด “remove” ต้องระวังให้มาก
ส่วนตัวคิดว่าควรใช้แค่
setTextไปเลย และไม่เปิดให้ผู้ใช้ใส่ HTML ได้เลยจะดีกว่าsetHTMLก็จะไม่เกิด XSSเมื่อดูจากความเป็นจริงที่
innerHTMLถูกใช้บ่อย ก็ยากจะตัดมันออกไปทั้งหมดน่าประทับใจที่ตอนนี้ทุกแง่มุมของการเข้าถึงเครือข่ายถูกควบคุมอย่างเหมาะสมแล้ว ทำให้ ห่วงโซ่ความปลอดภัย ย้ายจากการเชื่อถือโค้ดไปสู่การเชื่อถือการตั้งค่าโฮสต์
และค่าเริ่มต้นก็ถูกตั้งมาให้ปลอดภัยด้วย
สิ่งที่ผมอยากได้จริง ๆ คือ
<sandbox>element ที่สามารถ รันโค้ดอันตรายได้อย่างปลอดภัยไม่ใช่การไปแก้ไขโค้ดอันตราย แต่เป็นการเปิดให้มันทำงานในสภาพแวดล้อมที่แยกขาด
iframe มีข้อจำกัดตรงที่มันไม่สามารถไหลไปพร้อมกับ DOM ได้ และในยุคที่ AI กับคอนเทนต์แบบไดนามิกเพิ่มมากขึ้น เราต้องการ การห่อหุ้มที่ประกอบได้
ผมชอบชื่อ
setHTMLUnsafeมากฟีเจอร์ด้านความปลอดภัยมักล้มเหลวถ้าต้องให้ผู้พัฒนา opt-in เอง
ทางที่ได้ผลกว่าคือทำให้ “เส้นทางที่อันตรายดูอันตรายสมชื่อ”
ชื่อ
set_html()เข้าใจได้ตรงไปตรงมา กว่าinner_htmlมากAPI ของ JavaScript ช่างสะเปะสะปะจริง ๆ และสักวันคงต้องมีการจัดระเบียบใหม่
การถกเถียงครั้งนี้จะเน้นเรื่องความปลอดภัยก็จริง แต่เวลาเปิดตัว API ใหม่ ตัวการออกแบบเองก็ควรเรียบร้อยด้วย
DOM API ให้ความรู้สึกมาตั้งแต่อดีตจนถึงทุกวันนี้ว่า “เหมือนคนที่ไม่เคยออกแบบ API มาก่อนเป็นคนสร้าง”
นักพัฒนายุค 90s:
นักพัฒนายุค 2020s:
พวกเราไม่ได้เรียนรู้อะไรจากยุค 90s เลย