Cross-site Scripting (DOM XSS) ที่อิงตาม DOM เกิดขึ้นเมื่อข้อมูลจากa
_แหล่งที่มา_ ซึ่งผู้ใช้ควบคุมได้ (เช่น ชื่อผู้ใช้หรือ URL เปลี่ยนเส้นทางที่นำมาจากส่วนย่อย URL
) ไปถึง _Sink_ ซึ่งเป็นฟังก์ชันอย่าง eval() หรือตัวตั้งค่าพร็อพเพอร์ตี้
อย่าง .innerHTML ที่สามารถเรียกใช้โค้ด JavaScript ที่กำหนดเองได้
DOM XSS เป็นหนึ่งในช่องโหว่ด้านความปลอดภัยของเว็บที่พบบ่อยที่สุด และทีมพัฒนาอาจเพิ่มช่องโหว่นี้ลงในแอปโดยไม่ได้ตั้งใจ Trusted Types มี เครื่องมือให้คุณเขียน ตรวจสอบความปลอดภัย และทำให้แอปพลิเคชันไม่มีช่องโหว่ DOM XSS โดยทำให้ฟังก์ชัน Web API ที่เป็นอันตรายปลอดภัยโดยค่าเริ่มต้น Trusted Types พร้อมใช้งานเป็น Polyfill สำหรับเบราว์เซอร์ที่ยังไม่รองรับ
ฉากหลัง
DOM XSS เป็นหนึ่งในช่องโหว่ด้านความปลอดภัยของเว็บที่พบมากที่สุดและเป็นอันตรายที่สุดมาเป็นเวลาหลายปี
Cross-site Scripting มี 2 ประเภท ช่องโหว่ XSS บางอย่างเกิดจากโค้ดฝั่งเซิร์ฟเวอร์ที่สร้างโค้ด HTML ที่ประกอบขึ้นเป็นเว็บไซต์อย่างไม่ปลอดภัย ส่วนช่องโหว่อื่นๆ มีสาเหตุหลักอยู่ที่ไคลเอ็นต์ ซึ่งโค้ด JavaScript จะเรียกฟังก์ชันที่เป็นอันตรายด้วยเนื้อหาที่ผู้ใช้ควบคุมได้
หากต้องการป้องกัน XSS ฝั่งเซิร์ฟเวอร์, ให้หลีกเลี่ยงการสร้าง HTML โดยการเชื่อมสตริง ให้ใช้ไลบรารีเทมเพลตการหลีกหนีตามบริบทโดยอัตโนมัติที่ปลอดภัย แทน รวมถึง นโยบายรักษาความปลอดภัยเนื้อหาที่อิงตาม Nonce เพื่อลดข้อบกพร่องเพิ่มเติม
ตอนนี้เบราว์เซอร์ยังช่วยป้องกัน DOM XSS ที่อิงตาม DOM ฝั่งไคลเอ็นต์ได้ด้วยการใช้ Trusted Types
ข้อมูลเบื้องต้นเกี่ยวกับ API
Trusted Types ทำงานโดยการล็อกฟังก์ชัน Sink ที่มีความเสี่ยงต่อไปนี้ คุณอาจ รู้จักฟังก์ชันบางอย่างอยู่แล้ว เนื่องจากผู้ให้บริการเบราว์เซอร์และ เฟรมเวิร์กเว็บ แนะนำให้คุณหลีกเลี่ยงการใช้ฟีเจอร์เหล่านี้ด้วยเหตุผลด้านความปลอดภัย
- การจัดการสคริปต์:
<script src>และการตั้งค่าเนื้อหาข้อความของ<script>องค์ประกอบ - การสร้าง HTML จากสตริง:
- การเรียกใช้เนื้อหาปลั๊กอิน:
- การคอมไพล์โค้ด JavaScript ในรันไทม์:
evalsetTimeoutsetIntervalnew Function()
Trusted Types กำหนดให้คุณต้องประมวลผลข้อมูลก่อนที่จะส่งข้อมูลไปยังฟังก์ชัน Sink เหล่านี้ การใช้สตริงเพียงอย่างเดียวจะล้มเหลว เนื่องจากเบราว์เซอร์ไม่ทราบว่าข้อมูลเชื่อถือได้หรือไม่
anElement.innerHTML = location.href;
หากต้องการระบุว่าข้อมูลได้รับการประมวลผลอย่างปลอดภัย ให้สร้างออบเจ็กต์พิเศษ ซึ่งก็คือ Trusted Type
anElement.innerHTML = aTrustedHTML;
TrustedHTML
สำหรับ Sink ที่คาดหวังข้อมูลโค้ด HTML นอกจากนี้ยังมี
TrustedScript และ TrustedScriptURL ออบเจ็กต์สำหรับ Sink ที่มีความละเอียดอ่อนอื่นๆ
Trusted Types ช่วยลดพื้นผิวการโจมตี DOM XSS ของแอปพลิเคชันได้อย่างมาก โดยจะลดความซับซ้อนในการตรวจสอบความปลอดภัย และช่วยให้คุณบังคับใช้การตรวจสอบความปลอดภัยตามประเภทที่ทำเมื่อคอมไพล์ Lint หรือรวมโค้ดที่รันไทม์ในเบราว์เซอร์
วิธีใช้ Trusted Types
เตรียมพร้อมสำหรับรายงานการละเมิดนโยบายรักษาความปลอดภัยเนื้อหา
คุณสามารถติดตั้งใช้งานตัวรวบรวมรายงาน เช่น reporting-api-processor หรือ go-csp-collector แบบโอเพนซอร์ส หรือใช้ตัวรวบรวมรายงานที่เทียบเท่าแบบเชิงพาณิชย์ นอกจากนี้ คุณยังเพิ่มการบันทึกที่กำหนดเองและแก้ไขข้อบกพร่องในการละเมิดในเบราว์เซอร์ได้โดยใช้ ReportingObserver ดังนี้
const observer = new ReportingObserver((reports, observer) => {
for (const report of reports) {
if (report.type !== 'csp-violation' ||
report.body.effectiveDirective !== 'require-trusted-types-for') {
continue;
}
const violation = report.body;
console.log('Trusted Types Violation:', violation);
// ... (rest of your logging and reporting logic)
}
}, { buffered: true });
observer.observe();
หรือโดยการเพิ่ม Listener เหตุการณ์ ดังนี้
document.addEventListener('securitypolicyviolation',
console.error.bind(console));
เพิ่มส่วนหัว CSP แบบรายงานเท่านั้น
เพิ่มส่วนหัวการตอบกลับ HTTP ต่อไปนี้ลงในเอกสารที่คุณต้องการย้ายข้อมูลไปยัง Trusted Types
Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
ตอนนี้ระบบจะรายงานการละเมิดทั้งหมดไปยัง //my-csp-endpoint.example แต่เว็บไซต์จะยังคงทำงานต่อไป ส่วนถัดไปจะอธิบายวิธีทำงานของ //my-csp-endpoint.example
ระบุการละเมิด Trusted Types
นับจากนี้ไป ทุกครั้งที่ Trusted Types ตรวจพบการละเมิด เบราว์เซอร์จะส่งรายงานไปยัง report-uri ที่กำหนดค่าไว้ ตัวอย่างเช่น เมื่อแอปพลิเคชันส่งสตริงไปยัง innerHTML เบราว์เซอร์จะส่งรายงานต่อไปนี้
{
"csp-report": {
"document-uri": "https://my.url.example",
"violated-directive": "require-trusted-types-for",
"disposition": "report",
"blocked-uri": "trusted-types-sink",
"line-number": 39,
"column-number": 12,
"source-file": "https://my.url.example/script.js",
"status-code": 0,
"script-sample": "Element innerHTML <img src=x"
}
}
รายงานนี้ระบุว่าใน https://my.url.example/script.js ในบรรทัดที่ 39 มีการเรียก innerHTML ด้วยสตริงที่ขึ้นต้นด้วย <img src=x ข้อมูลนี้จะช่วยให้คุณจำกัดขอบเขตของโค้ดที่อาจทำให้เกิด DOM XSS และต้องมีการเปลี่ยนแปลง
แก้ไขการละเมิด
การแก้ไขการละเมิด Trusted Type ทำได้ 2 วิธี คุณสามารถ นำโค้ดที่ทำให้เกิดปัญหาออก, ใช้ไลบรารี, สร้างนโยบาย Trusted Type หรือสร้างนโยบายเริ่มต้นเป็นทางเลือกสุดท้าย
เขียนโค้ดที่ทำให้เกิดปัญหาใหม่
โค้ดที่ไม่เป็นไปตามข้อกำหนดอาจไม่จำเป็นอีกต่อไป หรือสามารถเขียนใหม่ได้โดยไม่ต้องใช้ฟังก์ชันที่ทำให้เกิดการละเมิด ดังนี้
el.textContent = ''; const img = document.createElement('img'); img.src = 'xyz.jpg'; el.appendChild(img);
el.innerHTML = '<img src=xyz.jpg>';
ใช้ไลบรารี
ไลบรารีบางรายการสร้าง Trusted Types ที่คุณส่งไปยังฟังก์ชัน Sink ได้อยู่แล้ว ตัวอย่างเช่น คุณสามารถใช้ DOMPurify เพื่อล้างข้อมูลโค้ด HTML โดยนำเพย์โหลด XSS ออก
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});
DOMPurify รองรับ Trusted Types
และแสดงผล HTML ที่ล้างข้อมูลแล้วซึ่งรวมอยู่ในออบเจ็กต์ TrustedHTML เพื่อไม่ให้เบราว์เซอร์
สร้างการละเมิด
สร้างนโยบาย Trusted Type
บางครั้งคุณไม่สามารถนำโค้ดที่ทำให้เกิดการละเมิดออก และไม่มีไลบรารีที่จะล้างข้อมูลค่าและสร้าง Trusted Type ให้คุณ ในกรณีดังกล่าว คุณสามารถสร้างออบเจ็กต์ Trusted Type ด้วยตนเองได้
ขั้นแรก ให้สร้างนโยบาย นโยบายเป็นโรงงานสำหรับ Trusted Types ที่บังคับใช้กฎความปลอดภัยบางอย่างกับอินพุต
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
createHTML: string => string.replace(/\</g, '<')
});
}
โค้ดนี้สร้างนโยบายชื่อ myEscapePolicy ที่สามารถสร้างออบเจ็กต์ TrustedHTML ได้โดยใช้ฟังก์ชัน createHTML() กฎที่กำหนดจะหลีกหนีอักขระ < ของ HTML เพื่อป้องกันการสร้างองค์ประกอบ HTML ใหม่
ใช้นโยบายดังนี้
const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML); // true
el.innerHTML = escaped; // '<img src=x onerror=alert(1)>'
ใช้นโยบายเริ่มต้น
บางครั้งคุณไม่สามารถเปลี่ยนโค้ดที่ทำให้เกิดปัญหาได้ เช่น หากคุณโหลดไลบรารีของบุคคลที่สามจาก CDN ในกรณีดังกล่าว ให้ใช้นโยบายเริ่มต้น :
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
trustedTypes.createPolicy('default', {
createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
});
}
ระบบจะใช้นโยบายชื่อ default ทุกที่ที่ใช้สตริงใน Sink ที่ยอมรับเฉพาะ Trusted Type
เปลี่ยนไปบังคับใช้นโยบายรักษาความปลอดภัยเนื้อหา
เมื่อแอปพลิเคชันไม่สร้างการละเมิดอีกต่อไป คุณจะเริ่มบังคับใช้ Trusted Types ได้
Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
ตอนนี้ไม่ว่าเว็บแอปพลิเคชันของคุณจะซับซ้อนเพียงใด สิ่งเดียวที่อาจทำให้เกิดช่องโหว่ DOM XSS คือโค้ดในนโยบายใดนโยบายหนึ่ง และคุณสามารถล็อกโค้ดนั้นได้มากยิ่งขึ้นโดยการจำกัดการสร้างนโยบาย