Chrome 67 บนเดสก์ท็อปมีฟีเจอร์ใหม่ที่เรียกว่าการแยกเว็บไซต์โดยค่าเริ่มต้น บทความนี้จะอธิบายว่าการแยกเว็บไซต์เกี่ยวข้องกับอะไร เหตุใดจึงมีความสำคัญ และเหตุผลที่นักพัฒนาเว็บควรตระหนักถึงเรื่องนี้
การแยกเว็บไซต์คืออะไร
อินเทอร์เน็ตมีไว้สำหรับดูวิดีโอเกี่ยวกับแมวและจัดการกระเป๋าเงินคริปโตเคอเรนซี และอื่นๆ อีกมากมาย
แต่คุณคงไม่อยากให้ fluffycats.example
มีสิทธิ์เข้าถึงคริปโตเคอเรนซีมูลค่าของคุณ! โชคดีที่เว็บไซต์ต่างๆ มักเข้าถึงข้อมูลของกันและกันภายในเบราว์เซอร์ไม่ได้ตามนโยบายต้นทางเดียวกัน อย่างไรก็ตาม เว็บไซต์ที่เป็นอันตรายอาจพยายามหลบเลี่ยงนโยบายนี้เพื่อโจมตีเว็บไซต์อื่น และบางครั้งคุณอาจพบข้อบกพร่องด้านความปลอดภัยในโค้ดของเบราว์เซอร์ที่บังคับใช้นโยบายต้นทางเดียวกัน ทีม Chrome มีเป้าหมายที่จะแก้ไขข้อบกพร่องดังกล่าวโดยเร็วที่สุด
การแยกเว็บไซต์เป็นฟีเจอร์ด้านความปลอดภัยใน Chrome ที่มีการป้องกันเพิ่มขึ้นอีก 1 ชั้นเพื่อไม่ให้การโจมตีดังกล่าวมีโอกาสสำเร็จน้อยลง ช่วยดูแลให้หน้าเว็บจากเว็บไซต์ต่างๆ เข้าสู่กระบวนการที่แตกต่างกันเสมอ โดยแต่ละหน้าเว็บจะทำงานในแซนด์บ็อกซ์ที่จำกัดขั้นตอนที่อนุญาตให้ทำได้ และยังบล็อกกระบวนการไม่ให้รับข้อมูลที่ละเอียดอ่อนบางประเภทจากเว็บไซต์อื่นๆ อีกด้วย ด้วยเหตุนี้ การแยกเว็บไซต์จึงทำให้เว็บไซต์ที่เป็นอันตรายใช้การโจมตีแบบ Side-channel แบบคาดเดาได้ยาก เช่น Spectre เพื่อขโมยข้อมูลจากเว็บไซต์อื่น เมื่อทีม Chrome ดำเนินการบังคับใช้เพิ่มเติมจนเสร็จสิ้นแล้ว การแยกเว็บไซต์จะช่วยแม้ว่าหน้าของผู้โจมตีจะละเมิดกฎบางอย่างในกระบวนการของตนเองได้
การแยกเว็บไซต์มีประสิทธิภาพทำให้เว็บไซต์ที่ไม่น่าเชื่อถือเข้าถึงหรือขโมยข้อมูลจากบัญชีในเว็บไซต์อื่นๆ ได้ยากขึ้น ซึ่งจะให้การป้องกันเพิ่มเติมสำหรับข้อบกพร่องด้านความปลอดภัยประเภทต่างๆ เช่น การโจมตีช่องด้านข้างของ Meltdown และ Spectre ครั้งล่าสุด
ดูรายละเอียดเพิ่มเติมเกี่ยวกับการแยกเว็บไซต์ได้ที่บทความในบล็อกด้านความปลอดภัยของ Google
การบล็อกการอ่านแบบข้ามต้นทาง
แม้ว่าหน้าแบบข้ามเว็บไซต์ทั้งหมดจะถูกจัดกระบวนการแยกกัน แต่หน้าเว็บก็ยังคงสามารถขอทรัพยากรย่อยแบบข้ามเว็บไซต์บางรายการได้อย่างถูกต้อง เช่น รูปภาพและ JavaScript หน้าเว็บที่เป็นอันตรายอาจใช้องค์ประกอบ <img>
เพื่อโหลดไฟล์ JSON ที่มีข้อมูลที่ละเอียดอ่อน เช่น ยอดคงเหลือในธนาคาร
<img src="https://your-bank.example/balance.json" />
<!-- Note: the attacker refused to add an `alt` attribute, for extra evil points. -->
หากไม่มีการแยกเว็บไซต์ เนื้อหาของไฟล์ JSON จะส่งเนื้อหาไปยังหน่วยความจำของกระบวนการแสดงผล ซึ่งในจุดนี้โหมดแสดงภาพจะสังเกตเห็นว่าไม่ใช่รูปแบบรูปภาพที่ถูกต้องและจะไม่แสดงผลรูปภาพ แต่ผู้โจมตีก็อาจใช้ประโยชน์จากช่องโหว่อย่างเช่นสเปคเตอร์เพื่อดูหน่วยความจำจำนวนมากได้
ผู้โจมตีอาจใช้ <script>
เพื่อส่งข้อมูลที่ละเอียดอ่อนไปยังหน่วยความจำแทนที่จะใช้ <img>
ได้ด้วย
<script src="https://your-bank.example/balance.json"></script>
การบล็อกการอ่านแบบข้ามต้นทางหรือ CORB เป็นฟีเจอร์ความปลอดภัยใหม่ที่ป้องกันไม่ให้เนื้อหาของ balance.json
เข้าสู่หน่วยความจำของหน่วยความจำประมวลผลการแสดงผลโดยอิงตามประเภท MIME เลย
มาดูรายละเอียดของวิธีการทำงานของ CORB กัน เว็บไซต์ขอทรัพยากรได้ 2 ประเภทจากเซิร์ฟเวอร์ ดังนี้
- แหล่งข้อมูลข้อมูล เช่น เอกสาร HTML, XML หรือ JSON
- แหล่งข้อมูลสื่อ เช่น รูปภาพ, JavaScript, CSS หรือแบบอักษร
เว็บไซต์รับทรัพยากรข้อมูลจากต้นทางของตนเองได้หรือจากต้นทางอื่นๆ ที่มีส่วนหัวที่อนุญาตของ CORS เช่น Access-Control-Allow-Origin: *
ในทางตรงกันข้าม คุณรวมทรัพยากรสื่อจากต้นทางใดก็ได้ แม้จะไม่มีส่วนหัว CORS ที่อนุญาตก็ตาม
CORB ป้องกันไม่ให้กระบวนการแสดงผลรับแหล่งข้อมูลแบบข้ามต้นทาง (เช่น HTML, XML หรือ JSON) ในกรณีต่อไปนี้
- ทรัพยากรมีส่วนหัว
X-Content-Type-Options: nosniff
- CORS ไม่ได้อนุญาตการเข้าถึงทรัพยากรอย่างชัดเจน
หากแหล่งข้อมูลแบบข้ามต้นทางไม่ได้ตั้งค่าส่วนหัว X-Content-Type-Options: nosniff
ไว้ CORB จะพยายามดักจับเนื้อหาการตอบสนองเพื่อระบุว่าเป็น HTML, XML หรือ JSON ซึ่งเป็นสิ่งจำเป็นเนื่องจากเว็บเซิร์ฟเวอร์บางแห่งกำหนดค่าไม่ถูกต้องและแสดงอิมเมจเป็น text/html
เป็นต้น
ทรัพยากรข้อมูลที่ถูกบล็อกโดยนโยบาย CORB จะแสดงต่อกระบวนการว่าว่างเปล่า แม้ว่าคำขอจะยังคงเกิดขึ้นในเบื้องหลัง ด้วยเหตุนี้ หน้าเว็บที่เป็นอันตรายจึงพบปัญหาในการดึงข้อมูลแบบข้ามเว็บไซต์เข้าสู่กระบวนการเพื่อขโมย
เราขอแนะนำสิ่งต่อไปนี้เพื่อการรักษาความปลอดภัยที่ดีที่สุดและใช้ประโยชน์จาก CORB
- ทำเครื่องหมายคำตอบด้วยส่วนหัว
Content-Type
ที่ถูกต้อง (เช่น ทรัพยากร HTML ควรแสดงผลเป็นtext/html
, ทรัพยากร JSON ที่มีประเภท MIME JSON และทรัพยากร XML ที่มีประเภท MIME ของ XML) - เลือกไม่ใช้การดักจับโดยใช้ส่วนหัว
X-Content-Type-Options: nosniff
หากไม่มีส่วนหัวนี้ Chrome จะทำการวิเคราะห์เนื้อหาอย่างรวดเร็วเพื่อยืนยันว่าประเภทถูกต้อง แต่เนื่องจากข้อผิดพลาดในการอนุญาตการตอบกลับเพื่อหลีกเลี่ยงการบล็อกสิ่งต่างๆ เช่น ไฟล์ JavaScript คุณจึงจะยืนยันตัวเองให้ทำสิ่งที่ถูกต้องได้ดีกว่า
ดูรายละเอียดเพิ่มเติมได้ที่บทความ CORB สำหรับนักพัฒนาเว็บหรือคำอธิบาย CORB เชิงลึก
เหตุใดนักพัฒนาเว็บจึงควรให้ความสำคัญกับการแยกเว็บไซต์
โดยส่วนใหญ่แล้ว การแยกเว็บไซต์เป็นฟีเจอร์เบื้องหลังของเบราว์เซอร์ที่ไม่เปิดเผยต่อนักพัฒนาเว็บโดยตรง เช่น ไม่มี API ที่เผยแพร่บนเว็บแบบใหม่ให้เรียนรู้ โดยทั่วไป หน้าเว็บไม่ควรบอกความแตกต่างได้เมื่อใช้งานโดยใช้หรือไม่ใช้การแยกเว็บไซต์
อย่างไรก็ตาม กฎนี้มีข้อยกเว้นบางประการ การเปิดใช้การแยกเว็บไซต์จะก่อให้เกิดผลข้างเคียงเล็กๆ น้อยๆ ที่อาจส่งผลต่อเว็บไซต์ของคุณ เราได้จัดทำรายการปัญหาที่ทราบเกี่ยวกับการแยกเว็บไซต์และได้อธิบายปัญหาที่สำคัญที่สุดไว้ด้านล่าง
เลย์เอาต์แบบเต็มหน้าไม่เป็นแบบซิงโครนัสอีกต่อไป
การแยกเว็บไซต์ไม่รับประกันว่าเลย์เอาต์แบบเต็มหน้าจะเป็นแบบซิงโครนัสอีกต่อไป เนื่องจากเฟรมของหน้าเว็บอาจกระจายไปในหลายกระบวนการ ซึ่งอาจส่งผลกระทบต่อหน้าหากระบบคิดว่าการเปลี่ยนแปลงเลย์เอาต์จะเผยแพร่ไปยังทุกเฟรมในหน้าทันที
ลองมาดูตัวอย่างเว็บไซต์ชื่อ fluffykittens.example
ซึ่งสื่อสารกับวิดเจ็ตโซเชียลที่โฮสต์บน social-widget.example
กัน
<!-- https://fluffykittens.example/ -->
<iframe src="https://social-widget.example/" width="123"></iframe>
<script>
const iframe = document.querySelector('iframe');
iframe.width = 456;
iframe.contentWindow.postMessage(
// The message to send:
'Meow!',
// The target origin:
'https://social-widget.example'
);
</script>
ในตอนแรก ความกว้างของวิดเจ็ตโซเชียล <iframe>
คือ 123
พิกเซล แต่จากนั้นหน้า FluffyKittens เปลี่ยนความกว้างเป็น 456
พิกเซล (ทริกเกอร์เลย์เอาต์) และส่งข้อความไปยังวิดเจ็ตโซเชียลซึ่งมีโค้ดต่อไปนี้
<!-- https://social-widget.example/ -->
<script>
self.onmessage = () => {
console.log(document.documentElement.clientWidth);
};
</script>
เมื่อใดก็ตามที่วิดเจ็ตโซเชียลได้รับข้อความผ่าน postMessage
API ก็จะบันทึกความกว้างขององค์ประกอบ <html>
ราก
ระบบจะบันทึกค่าความกว้างใด ก่อนที่ Chrome จะเปิดใช้การแยกเว็บไซต์ คำตอบคือ 456
การเข้าถึง document.documentElement.clientWidth
จะบังคับใช้เลย์เอาต์ ซึ่งเคยเป็นแบบซิงโครนัสก่อนที่ Chrome จะเปิดใช้การแยกเว็บไซต์ อย่างไรก็ตาม เมื่อเปิดใช้การแยกเว็บไซต์แล้ว การจัดเรียงวิดเจ็ตโซเชียลแบบข้ามต้นทางจะเกิดขึ้นแบบไม่พร้อมกันในกระบวนการที่แยกต่างหาก ดังนั้น คำตอบที่ได้จะเป็น 123
ด้วย ซึ่งก็คือค่า width
เดิม
หากหน้าเว็บเปลี่ยนแปลงขนาดของ <iframe>
แบบข้ามต้นทางแล้วส่ง postMessage
ไปยังหน้าดังกล่าว เมื่อใช้การแยกเว็บไซต์ เฟรมที่ได้รับอาจยังไม่ทราบขนาดใหม่เมื่อได้รับข้อความ โดยทั่วไป หน้าอาจทำให้หน้าเสียหายหากคิดว่าการเปลี่ยนเลย์เอาต์เผยแพร่ไปยังทุกเฟรมในหน้าทันที
ในตัวอย่างนี้ โซลูชันที่มีประสิทธิภาพมากกว่าจะตั้งค่า width
ในเฟรมระดับบนสุด และตรวจจับการเปลี่ยนแปลงใน <iframe>
ด้วยการฟังเหตุการณ์ resize
ตัวแฮนเดิลการยกเลิกการโหลดอาจหมดเวลาบ่อยขึ้น
เมื่อเฟรมไปยังส่วนต่างๆ หรือปิด เอกสารเก่าและเอกสารเฟรมย่อยที่ฝังอยู่ภายในจะเรียกใช้ตัวแฮนเดิล unload
ทั้งหมด หากการนำทางใหม่เกิดขึ้นในกระบวนการแสดงผลเดียวกัน (เช่น สำหรับการนำทางต้นทางเดียวกัน) เครื่องจัดการ unload
ของเอกสารเก่าและเฟรมย่อยของเอกสารจะทำงานเป็นเวลานานตามกำหนดก่อนที่จะอนุญาตให้การนำทางใหม่ยืนยัน
addEventListener('unload', () => {
doSomethingThatMightTakeALongTime();
});
ในสถานการณ์นี้ เครื่องจัดการ unload
ในเฟรมทั้งหมดเชื่อถือได้มาก
อย่างไรก็ตาม แม้จะไม่มีการแยกเว็บไซต์ การนำทางเฟรมหลักบางส่วนจะเป็นแบบข้ามกระบวนการ ซึ่งจะส่งผลต่อการทำงานของตัวแฮนเดิลการยกเลิกการโหลด ตัวอย่างเช่น หากพิมพ์ URL จาก old.example
ไปยัง new.example
ในแถบที่อยู่ การไปยังส่วนต่างๆ ของ new.example
จะเกิดขึ้นในกระบวนการใหม่ ตัวแฮนเดิลการยกเลิกการโหลดสำหรับ old.example
และเฟรมย่อยจะทำงานในกระบวนการ old.example
ในเบื้องหลัง หลังจากที่หน้า new.example
แสดงขึ้น และตัวแฮนเดิลการยกเลิกการโหลดเดิมจะสิ้นสุดลงหากไม่ดำเนินการภายในระยะหมดเวลาที่กำหนด เนื่องจากตัวแฮนเดิลการยกเลิกการโหลดอาจทำงานไม่เสร็จก่อนหมดเวลา ลักษณะการยกเลิกการโหลดจึงเชื่อถือได้น้อยลง
เมื่อใช้การแยกเว็บไซต์ การไปยังส่วนต่างๆ แบบข้ามเว็บไซต์ทั้งหมดจะกลายเป็นแบบข้ามกระบวนการ เพื่อให้เอกสารจากเว็บไซต์ต่างๆ ไม่มีการแชร์กระบวนการซึ่งกันและกัน สถานการณ์ข้างต้นจึงมีผลกับกรณีอื่นๆ มากขึ้น และตัวแฮนเดิลการยกเลิกการโหลดใน <iframe>
มักจะมีลักษณะการทำงานในเบื้องหลังและระยะหมดเวลาตามที่อธิบายไว้ข้างต้น
ความแตกต่างอีกข้อที่เกิดจากการแยกเว็บไซต์คือลำดับใหม่ของตัวแฮนเดิลการยกเลิกการโหลดแบบพร้อมกัน ซึ่งไม่มีการแยกเว็บไซต์ ตัวแฮนเดิลการยกเลิกการโหลดจะทำงานตามลำดับจากบนลงล่างแบบเข้มงวดในทุกเฟรม แต่เมื่อใช้การแยกเว็บไซต์ ตัวแฮนเดิลการยกเลิกการโหลดจะทำงานพร้อมกันในกระบวนการต่างๆ
ซึ่งเป็นผลพื้นฐานพื้นฐานของการเปิดใช้การแยกเว็บไซต์ ทีม Chrome กำลังพยายามปรับปรุงความน่าเชื่อถือของตัวแฮนเดิลการยกเลิกการโหลดสำหรับการใช้งานทั่วไป หากทำได้ เราทราบถึงข้อบกพร่องที่ตัวแฮนเดิลการยกเลิกการโหลดเฟรมย่อยยังไม่สามารถใช้ฟีเจอร์บางอย่างได้และกำลังดำเนินการแก้ไข
กรณีที่สำคัญสำหรับตัวแฮนเดิลการยกเลิกการโหลดคือการส่งคำสั่ง ping เมื่อสิ้นสุดเซสชัน ซึ่งโดยปกติจะมีการดำเนินการดังต่อไปนี้
addEventListener('pagehide', () => {
const image = new Image();
img.src = '/end-of-session';
});
แนวทางที่ดีกว่าและมีประสิทธิภาพมากกว่าเพื่อให้สอดคล้องกับการเปลี่ยนแปลงนี้คือการใช้ navigator.sendBeacon
แทน ดังนี้
addEventListener('pagehide', () => {
navigator.sendBeacon('/end-of-session');
});
หากต้องการควบคุมคำขอมากขึ้น ให้ใช้ตัวเลือก keepalive
ของ Fetch API ดังนี้
addEventListener('pagehide', () => {
fetch('/end-of-session', {keepalive: true});
});
บทสรุป
การแยกเว็บไซต์ทำให้เว็บไซต์ที่ไม่น่าเชื่อถือเข้าถึงหรือขโมยข้อมูลจากบัญชีของคุณในเว็บไซต์อื่นๆ ได้ยากขึ้นโดยการแยกแต่ละเว็บไซต์ในกระบวนการของตนเอง ด้วยเหตุนี้ CORB จะพยายามกันไม่ให้ทรัพยากรข้อมูลที่ละเอียดอ่อน อยู่ในกระบวนการแสดงผล คำแนะนำข้างต้นของเราช่วยให้คุณ ได้รับประโยชน์สูงสุดจากฟีเจอร์ความปลอดภัยใหม่เหล่านี้
ขอขอบคุณ Alex Moshchuk, Charlie Reis, Jason Miller, Nasko Oskov, Philip Walton, Shubhie Panicker และ Thomas Steiner สำหรับการอ่านฉบับร่างของบทความนี้และแสดงความคิดเห็น