Sanitizer API ใหม่มีเป้าหมายที่จะสร้างตัวประมวลผลที่มีประสิทธิภาพสำหรับการใส่สตริงที่กำหนดเองลงในหน้าเว็บอย่างปลอดภัย
แอปพลิเคชันจัดการกับสตริงที่ไม่น่าเชื่อถืออยู่ตลอดเวลา แต่การแสดงผลเนื้อหานั้นอย่างปลอดภัยโดยเป็นส่วนหนึ่งของเอกสาร HTML อาจเป็นเรื่องยุ่งยาก หากไม่มีการดูแลที่เพียงพอ อาจเกิดข้อผิดพลาดโดยไม่ได้ตั้งใจสำหรับ cross-site Scripting (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 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 ครั้งนี้ทำให้เสียเวลาในการประมวลผล แต่ก็อาจนำไปสู่ช่องโหว่ที่น่าสนใจซึ่งเกิดจากกรณีที่ผลลัพธ์ของการแยกวิเคราะห์ครั้งที่ 2 แตกต่างจากครั้งแรก
HTML ยังต้องได้รับการแยกวิเคราะห์บริบทด้วย ตัวอย่างเช่น <td>
เหมาะสมใน <table>
แต่ไม่ใช่ใน <div>
เนื่องจาก DOMPurify.sanitize()
จะรับเฉพาะสตริงเป็นอาร์กิวเมนต์ จึงต้องเดาบริบทการแยกวิเคราะห์
Sanitizer API ปรับปรุงมาจากวิธีการ DOMPurify และออกแบบมาเพื่อลดความจำเป็นในการแยกวิเคราะห์ 2 ครั้งและชี้แจงบริบทการแยกวิเคราะห์
สถานะ API และการสนับสนุนของเบราว์เซอร์
Sanitizer API กำลังอยู่ระหว่างการหารือเกี่ยวกับกระบวนการกำหนดมาตรฐาน และ Chrome อยู่ระหว่างการนำ API มาใช้
ขั้นตอน | สถานะ |
---|---|
1. สร้างคำอธิบาย | เสร็จสมบูรณ์ |
2. สร้างฉบับร่างข้อกำหนด | เสร็จสมบูรณ์ |
3. รวบรวมความคิดเห็นและทำซ้ำในการออกแบบ | เสร็จสมบูรณ์ |
4. ช่วงทดลองใช้ Chrome จากต้นทาง | เสร็จสมบูรณ์ |
5. เปิดตัว | ความตั้งใจที่จะจัดส่งใน M105 |
Mozilla: ถือว่าข้อเสนอนี้เป็นการสร้างต้นแบบและนำมาใช้งานจริง
WebKit: ดูการตอบกลับในรายชื่ออีเมลของ WebKit
วิธีเปิดใช้ Sanitizer API
การเปิดใช้ผ่านตัวเลือก about://flags
หรือ CLI
Chrome
Chrome กําลังนํามาใช้ Sanitizer API ใน Chrome 93 ขึ้นไป คุณลองใช้ลักษณะการทำงานดังกล่าวได้โดยเปิดใช้ Flag about://flags/#enable-experimental-web-platform-features
ใน Chrome Canary และเวอร์ชันที่กำลังพัฒนาเวอร์ชันก่อนหน้า คุณสามารถเปิดใช้ผ่าน --enable-blink-features=SanitizerAPI
และลองใช้ได้เลย อ่านวิธีการเรียกใช้ Chrome ที่มีการแฟล็ก
Firefox
นอกจากนี้ Firefox ยังใช้ Sanitizer API เป็นฟีเจอร์ทดลองอีกด้วย หากต้องการเปิดใช้ ให้ตั้งค่า Flag dom.security.sanitizer.enabled
เป็น true
ใน about:config
การตรวจหาฟีเจอร์
if (window.Sanitizer) {
// Sanitizer API is enabled
}
ความคิดเห็น
หากคุณลองใช้ API นี้และมีความคิดเห็นใดๆ เรายินดีรับฟัง แชร์ความคิดเห็นเกี่ยวกับปัญหาเกี่ยวกับ GitHub ของ Sanitizer API และพูดคุยกับผู้เขียนข้อมูลจำเพาะและผู้ที่สนใจ API นี้
หากคุณพบข้อบกพร่องหรือลักษณะการทำงานที่ไม่คาดคิดในการใช้งาน Chrome โปรดรายงานข้อบกพร่องเพื่อรายงานข้อบกพร่อง เลือกองค์ประกอบ Blink>SecurityFeature>SanitizerAPI
และแชร์รายละเอียดเพื่อช่วยผู้ปฏิบัติงานติดตามปัญหา
สาธิต
หากต้องการดูการทำงานของ Sanitizer API ที่ Sanitizer API Playground โดย Mike West
ข้อมูลอ้างอิง
- ข้อกำหนดของ HTML Sanitizer API
- ที่เก็บ WICG/sanitizer-api
- คำถามที่พบบ่อยเกี่ยวกับ Sanitizer API
- เอกสารอ้างอิง HTML Sanitizer API ใน MDN
รูปภาพโดย Towfiqu barbhuiya ใน Unsplash