Sanitizer API ใหม่มีเป้าหมายในการสร้างตัวประมวลผลที่มีประสิทธิภาพสำหรับการแทรกสตริงที่กำหนดเองเพื่อให้นำไปแทรกในหน้าเว็บได้อย่างปลอดภัย
แอปพลิเคชันต้องจัดการกับสตริงที่ไม่น่าเชื่อถือตลอดเวลา แต่การแสดงผลเนื้อหานั้นเป็นส่วนหนึ่งของเอกสาร HTML อย่างปลอดภัยอาจเป็นเรื่องยาก หากไม่ได้ได้รับการดูแลอย่างเพียงพอ โอกาสที่จะเกิดการเขียนสคริปต์ข้ามเว็บไซต์ (XSS) โดยไม่ตั้งใจ ซึ่งผู้โจมตีที่เป็นอันตรายอาจฉวยโอกาสได้ง่ายๆ
เพื่อลดความเสี่ยงดังกล่าว ข้อเสนอ Sanitizer API ใหม่มีเป้าหมายเพื่อสร้างตัวประมวลผลที่มีประสิทธิภาพสำหรับสตริงที่กำหนดเองเพื่อแทรกลงในหน้าเว็บได้อย่างปลอดภัย บทความนี้จะแนะนำ API และอธิบายการใช้งาน
// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerror=alert(0)>`, new Sanitizer())
การป้อนข้อมูลของผู้ใช้เป็น Escape
เมื่อแทรกอินพุตของผู้ใช้ สตริงการค้นหา เนื้อหาของคุกกี้ และอื่นๆ ลงใน DOM สตริงจะต้องกำหนดเป็นอักขระหลีกอย่างถูกต้อง ควรให้ความสนใจเป็นพิเศษกับการควบคุม DOM ผ่าน .innerHTML
ซึ่งสตริงที่ไม่ใช้ Escape เป็นแหล่งที่มาโดยทั่วไปของ XSS
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.innerHTML = user_input
หากกำหนดสัญลักษณ์พิเศษ HTML ในสตริงอินพุตด้านบนหรือขยายโดยใช้ .textContent
ระบบจะไม่ดำเนินการ alert(0)
อย่างไรก็ตาม เนื่องจาก <em>
ที่ผู้ใช้เพิ่มจะมีการขยายเป็นสตริงตามเดิมด้วย จึงไม่สามารถใช้วิธีนี้เพื่อเก็บการตกแต่งข้อความไว้ใน HTML ได้
วิธีที่ดีที่สุดคือการหลีกเลี่ยง แต่เป็นการทำความสะอาด
การปกปิดข้อมูลจากผู้ใช้
ความแตกต่างระหว่างการหลบหนีและการทำความสะอาด
การกำหนดเป็นอักขระหลีกหมายถึงการแทนที่อักขระ HTML พิเศษด้วยเอนทิตี HTML
การทำความสะอาดหมายถึงการนำส่วนที่เป็นอันตรายในเชิงความหมาย (เช่น การทำงานของสคริปต์) ออกจากสตริง HTML
ตัวอย่าง
ในตัวอย่างก่อนหน้านี้ <img onerror>
จะทำให้ตัวแฮนเดิลข้อผิดพลาดทำงานได้ แต่หากนำตัวแฮนเดิล onerror
ออกก็จะสามารถขยายตัวจัดการใน DOM ได้อย่างปลอดภัยในขณะที่ยังคง <em>
ไว้
// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerror=alert(0)>`
// Sanitized ⛑
$div.innerHTML = `<em>hello world</em><img src="">`
เพื่อการตรวจสอบความถูกต้องของสตริงอินพุตเป็น HTML ละเว้นแท็กและแอตทริบิวต์ที่ถือว่าเป็นอันตราย และเก็บแท็กที่ไม่เป็นอันตรายไว้
ข้อกำหนดของ Sanitizer API ที่เสนอมีวัตถุประสงค์เพื่อให้การประมวลผลแบบเดียวกับ API มาตรฐานสำหรับเบราว์เซอร์
API ของ Sanitizer
มีการใช้ Sanitizer API ในลักษณะดังนี้
const $div = document.querySelector('div')
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.setHTML(user_input, { sanitizer: new Sanitizer() }) // <div><em>hello world</em><img src=""></div>
แต่ { sanitizer: new Sanitizer() }
คืออาร์กิวเมนต์เริ่มต้น ซึ่งให้มีลักษณะดังต่อไปนี้
$div.setHTML(user_input) // <div><em>hello world</em><img src=""></div>
โปรดทราบว่า setHTML()
กำหนดไว้ในวันที่ Element
ด้วยเมธอดของ Element
บริบทในการแยกวิเคราะห์จะชัดแจ้งในตัวเอง (ในกรณีนี้คือ <div>
) การแยกวิเคราะห์จะดำเนินการภายในเพียงครั้งเดียว และผลลัพธ์จะถูกขยายไปยัง DOM โดยตรง
หากต้องการผลลัพธ์ของการทำความสะอาดเป็นสตริง ให้ใช้ .innerHTML
จากผลลัพธ์ของ setHTML()
const $div = document.createElement('div')
$div.setHTML(user_input)
$div.innerHTML // <em>hello world</em><img src="">
ปรับแต่งผ่านการกำหนดค่า
Sanitizer API จะได้รับการกำหนดค่าโดยค่าเริ่มต้นเพื่อนำสตริงที่จะทริกเกอร์การดำเนินการของสคริปต์ออก อย่างไรก็ตาม คุณยังเพิ่มการปรับแต่งของคุณเองลงในกระบวนการทำความสะอาดผ่านออบเจ็กต์การกำหนดค่าได้อีกด้วย
const config = {
allowElements: [],
blockElements: [],
dropElements: [],
allowAttributes: {},
dropAttributes: {},
allowCustomElements: true,
allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config)
ตัวเลือกต่อไปนี้ระบุว่าผลการทำความสะอาดควรจัดการกับองค์ประกอบที่ระบุอย่างไร
allowElements
: ชื่อองค์ประกอบที่น้ำยาทำความสะอาดควรเก็บไว้
blockElements
: ชื่อองค์ประกอบที่น้ำยาทำความสะอาดควรนำออกโดยที่ยังรักษาบุตรหลานไว้
dropElements
: ชื่อองค์ประกอบที่น้ำยาทำความสะอาดควรนำออกรวมถึงชื่อของบุตรหลาน
const str = `hello <b><i>world</i></b>`
$div.setHTML(str)
// <div>hello <b><i>world</i></b></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: [ "b" ]}) })
// <div>hello <b>world</b></div>
$div.setHTML(str, { sanitizer: new Sanitizer({blockElements: [ "b" ]}) })
// <div>hello <i>world</i></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: []}) })
// <div>hello world</div>
คุณยังควบคุมได้ว่าโปรแกรมทำความสะอาดจะอนุญาตหรือปฏิเสธแอตทริบิวต์ที่ระบุด้วยตัวเลือกต่อไปนี้
allowAttributes
dropAttributes
พร็อพเพอร์ตี้ allowAttributes
และ dropAttributes
คาดหวังว่าจะมีรายการการจับคู่แอตทริบิวต์ ซึ่งเป็นออบเจ็กต์ที่มีคีย์เป็นชื่อแอตทริบิวต์ และค่าคือรายการองค์ประกอบเป้าหมายหรือไวลด์การ์ด *
const str = `<span id=foo class=bar style="color: red">hello</span>`
$div.setHTML(str)
// <div><span id="foo" class="bar" style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["span"]}}) })
// <div><span style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["p"]}}) })
// <div><span>hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["*"]}}) })
// <div><span style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({dropAttributes: {"id": ["span"]}}) })
// <div><span class="bar" style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {}}) })
// <div>hello</div>
allowCustomElements
คือตัวเลือกในการอนุญาตหรือปฏิเสธองค์ประกอบที่กำหนดเอง หากได้รับอนุญาต การกำหนดค่าอื่นๆ สำหรับองค์ประกอบและแอตทริบิวต์จะยังคงมีผล
const str = `<custom-elem>hello</custom-elem>`
$div.setHTML(str)
// <div></div>
const sanitizer = new Sanitizer({
allowCustomElements: true,
allowElements: ["div", "custom-elem"]
})
$div.setHTML(str, { sanitizer })
// <div><custom-elem>hello</custom-elem></div>
แพลตฟอร์ม API
เปรียบเทียบกับ DomPurify
DOMPurify เป็นไลบรารีชื่อดังที่มีฟังก์ชันการทำความสะอาดข้อมูล ความแตกต่างที่สำคัญระหว่าง Sanitizer API กับ DOMPurify คือ DOMPurify จะแสดงผลผลลัพธ์ของการทำความสะอาดเป็นสตริง ซึ่งคุณต้องเขียนลงในองค์ประกอบ DOM ผ่าน .innerHTML
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
const sanitized = DOMPurify.sanitize(user_input)
$div.innerHTML = sanitized
// `<em>hello world</em><img src="">`
DOMPurify สามารถใช้เป็นรายการสำรองได้เมื่อไม่มีการใช้งาน Sanitizer API ในเบราว์เซอร์
การใช้งาน DOMPurify มีข้อเสีย 2 ข้อ หากมีการส่งคืนสตริง ระบบจะแยกวิเคราะห์สตริงอินพุต 2 ครั้งโดย DOMPurify และ .innerHTML
การแยกวิเคราะห์ซ้ำทำให้เสียเวลาในการประมวลผล แต่ก็อาจทำให้เกิดช่องโหว่ที่น่าสนใจซึ่งเกิดจากกรณีที่ผลลัพธ์ของการแยกวิเคราะห์ครั้งที่ 2 ต่างจากครั้งแรก
HTML จะต้องมีบริบทที่จะแยกวิเคราะห์ด้วย ตัวอย่างเช่น <td>
เข้าใจได้ใน <table>
แต่ไม่ใช่ใน <div>
เนื่องจาก DOMPurify.sanitize()
จะใช้สตริงเป็นอาร์กิวเมนต์เท่านั้น จึงต้องคาดเดาบริบทการแยกวิเคราะห์
Sanitizer API ปรับปรุงมาจากวิธีการของ DOMPurify และออกแบบมาเพื่อขจัดความจำเป็นในการแยกวิเคราะห์ 2 ครั้งและชี้แจงบริบทการแยกวิเคราะห์ให้ชัดเจน
สถานะ API และการสนับสนุนของเบราว์เซอร์
Sanitizer API อยู่ระหว่างการหารือในกระบวนการกำหนดมาตรฐาน และ Chrome อยู่ระหว่างการดำเนินการ
ขั้นตอน | สถานะ |
---|---|
1. สร้างข้อความอธิบาย | เสร็จสมบูรณ์ |
2. สร้างข้อกำหนดฉบับร่าง | เสร็จสมบูรณ์ |
3. รวบรวมความคิดเห็นและทำซ้ำเกี่ยวกับการออกแบบ | เสร็จสมบูรณ์ |
4. ช่วงทดลองใช้จากต้นทางของ Chrome | เสร็จสมบูรณ์ |
5. เปิดตัว | ตั้งใจที่จะจัดส่งใน M105 |
Mozilla: เห็นว่าข้อเสนอนี้ควรสร้างต้นแบบและนำข้อเสนอนี้ไปใช้อย่างจริงจัง
WebKit: ดูการตอบกลับในรายชื่ออีเมล WebKit
วิธีเปิดใช้ Sanitizer API
เปิดใช้ผ่านตัวเลือก about://flags
หรือ CLI
Chrome
Chrome อยู่ระหว่างการติดตั้งใช้งาน Sanitizer API ใน Chrome 93 ขึ้นไป คุณลองใช้ลักษณะการทำงานนี้ได้ด้วยการเปิดใช้แฟล็ก about://flags/#enable-experimental-web-platform-features
ใน Chrome Canary และเวอร์ชันที่กำลังพัฒนาเวอร์ชันก่อนหน้านี้ คุณจะเปิดใช้ได้ผ่าน --enable-blink-features=SanitizerAPI
และลองใช้งานเลย ดูวิธีการเรียกใช้ Chrome ที่มีแฟล็ก
Firefox
นอกจากนี้ Firefox ยังใช้ Sanitizer API เป็นฟีเจอร์ทดลองอีกด้วย หากต้องการเปิดใช้ ให้ตั้งค่าแฟล็ก dom.security.sanitizer.enabled
เป็น true
ใน about:config
การตรวจหาฟีเจอร์
if (window.Sanitizer) {
// Sanitizer API is enabled
}
ความคิดเห็น
หากคุณลองใช้ API นี้และมีความคิดเห็น เรายินดีรับฟังเสมอ แชร์ความคิดเห็นของคุณเกี่ยวกับปัญหาเกี่ยวกับ GitHub ของ Santizer และพูดคุยกับผู้เขียนข้อมูลจำเพาะและผู้ที่สนใจ API นี้
หากคุณพบข้อบกพร่องหรือลักษณะการทำงานที่ไม่คาดคิดในการใช้งาน Chrome ให้รายงานข้อบกพร่องเพื่อรายงานข้อบกพร่อง เลือกคอมโพเนนต์ Blink>SecurityFeature>SanitizerAPI
และแชร์รายละเอียดเพื่อช่วยให้ผู้ติดตั้งใช้งานติดตามปัญหาได้
ข้อมูลประชากร
ดูการทำงานของ Sanitizer API ได้ที่ Sanitizer API Playground โดย Mike West
รายการอ้างอิง
- ข้อกำหนด HTML Sanitizer API
- ที่เก็บ WICG/sanitizer-api
- คำถามที่พบบ่อยเกี่ยวกับ Santizer API
- เอกสารอ้างอิงสำหรับ HTML Sanitizer API เกี่ยวกับ MDN
รูปภาพโดย Towfiqu barbhuiya ใน Unsplash