Sanitizer API ใหม่มีเป้าหมายเพื่อสร้างตัวประมวลผลที่มีประสิทธิภาพสำหรับสตริงที่กำหนดเองเพื่อแทรกลงในหน้าเว็บอย่างปลอดภัย
แอปพลิเคชันต้องจัดการกับสตริงที่ไม่น่าเชื่อถืออยู่ตลอดเวลา แต่การแสดงผลเนื้อหาดังกล่าวอย่างปลอดภัยโดยเป็นส่วนหนึ่งของเอกสาร HTML อาจเป็นเรื่องยาก หากไม่ระมัดระวังมากพอ คุณอาจสร้างโอกาสให้เกิดการการเขียนสคริปต์ข้ามเว็บไซต์ (XSS) โดยไม่ได้ตั้งใจ ซึ่งผู้โจมตีที่เป็นอันตรายอาจใช้ประโยชน์ได้
เพื่อลดความเสี่ยงดังกล่าว ข้อเสนอ Sanitizer API ใหม่จึงมีเป้าหมายในการสร้างตัวประมวลผลที่มีประสิทธิภาพสำหรับสตริงที่กำหนดเองเพื่อแทรกลงในหน้าเว็บอย่างปลอดภัย บทความนี้จะแนะนำ API และอธิบายการใช้งาน
// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerror=alert(0)>`, new Sanitizer())
การหนีค่าที่ผู้ใช้ป้อน
เมื่อแทรกอินพุตของผู้ใช้ สตริงการค้นหา เนื้อหาคุกกี้ และอื่นๆ ลงใน DOM สตริงต้องได้รับการหลีกอย่างเหมาะสม โปรดระมัดระวังเป็นพิเศษในการดัดแปลง DOM ผ่าน .innerHTML
เนื่องจากสตริงที่ไม่มีการหลบเป็นแหล่งที่มาของ 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 มาตรฐานสำหรับเบราว์เซอร์
Sanitizer 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 กำลังอยู่ระหว่างการใช้งาน
ขั้นตอน | สถานะ |
---|---|
1. สร้างคำอธิบาย | เสร็จสมบูรณ์ |
2. สร้างฉบับร่างข้อกำหนด | เสร็จสมบูรณ์ |
3. รวบรวมความคิดเห็นและปรับปรุงการออกแบบ | เสร็จสมบูรณ์ |
4. ช่วงทดลองใช้จากต้นทางของ Chrome | เสร็จสมบูรณ์ |
5. เปิดตัว | Intent to Ship ใน 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 และช่อง Dev เวอร์ชันเก่า คุณสามารถเปิดใช้ผ่าน --enable-blink-features=SanitizerAPI
และลองใช้ได้เลย ดูวิธีการเรียกใช้ Chrome ด้วย Flag
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