การแยกเว็บไซต์สำหรับนักพัฒนาเว็บ

Chrome 67 ในเดสก์ท็อปมีฟีเจอร์ใหม่ชื่อการแยกเว็บไซต์ซึ่งเปิดใช้โดยค่าเริ่มต้น ช่วงเวลานี้ บทความจะอธิบายว่าการแยกเว็บไซต์เป็นเรื่องเกี่ยวกับอะไร ทำไมจึงจำเป็นต้องมี และทำไมนักพัฒนาเว็บจึงควร ตระหนักถึงเรื่องนี้

การแยกเว็บไซต์คืออะไร

อินเทอร์เน็ตมีไว้ดูวิดีโอแมวและจัดการกระเป๋าสตางค์คริปโตเคอเรนซี และอื่นๆ เช่น แต่คุณคงไม่อยากให้ fluffycats.example เข้าถึงคริปโตคอยน์ที่มีค่าของคุณ! โชคดี โดยทั่วไปเว็บไซต์ต่างๆ มักจะเข้าถึงข้อมูลของกันและกันภายในเบราว์เซอร์ไม่ได้เนื่องจากต้นทางเดียวกัน นโยบาย แต่ถึงกระนั้น เว็บไซต์ที่เป็นอันตรายก็อาจพยายามข้ามนโยบายนี้เพื่อโจมตีเว็บไซต์อื่นๆ และ ในบางครั้ง อาจพบข้อบกพร่องด้านความปลอดภัยในโค้ดของเบราว์เซอร์ที่บังคับใช้นโยบายต้นทางเดียวกัน ทีม Chrome ต้องการแก้ไขข้อบกพร่องดังกล่าวโดยเร็วที่สุด

การแยกเว็บไซต์ คือคุณลักษณะด้านความปลอดภัยใน Chrome ที่มีแนวป้องกันเพิ่มเติมเพื่อ การโจมตีดังกล่าวมีโอกาสน้อยที่จะประสบความสำเร็จ ช่วยให้มั่นใจได้ว่าหน้าเว็บจากเว็บไซต์ต่างๆ จะวาง แยกออกเป็นกระบวนการต่างๆ โดยแต่ละกระบวนการทำงานในแซนด์บ็อกซ์ซึ่งจำกัดสิ่งที่กระบวนการอนุญาตให้ทำได้ ทั้งยังบล็อกกระบวนการไม่ให้รับข้อมูลที่ละเอียดอ่อนบางประเภทจากเว็บไซต์อื่นๆ อีกด้วย เพื่อ การใช้การแยกเว็บไซต์จะทำให้เว็บไซต์ที่เป็นอันตรายใช้การคาดเดาได้ยากกว่า การโจมตีแบบ Side-channel เช่น สเปคเตอร์ เพื่อขโมยข้อมูลจากเว็บไซต์อื่น เมื่อทีม Chrome ดำเนินการจนเสร็จสิ้น การบังคับใช้เพิ่มเติม การแยกเว็บไซต์ยังมีประโยชน์แม้ว่าหน้าของผู้โจมตีอาจทำลายเนื้อหาบางอย่างได้ ของกฎต่างๆ ในกระบวนการของตัวเอง

การแยกเว็บไซต์อย่างมีประสิทธิภาพทำให้เว็บไซต์ที่ไม่น่าเชื่อถือเข้าถึงหรือขโมยข้อมูลได้ยากขึ้น จากบัญชีของคุณในเว็บไซต์อื่นๆ มอบการปกป้องเพิ่มเติมจาก ข้อบกพร่องด้านความปลอดภัย เช่น การโจมตีแบบ Side-channel จาก 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 จะปรากฏในหน่วยความจำของโปรแกรมแสดงผล ณ จุดนี้ โหมดแสดงภาพพบว่าไม่ใช่รูปแบบรูปภาพที่ถูกต้องและไม่แสดงผล รูปภาพ แต่ผู้โจมตีก็อาจใช้ประโยชน์จากช่องโหว่อย่างสเปคเตอร์เพื่ออ่านข้อมูล ส่วนของความทรงจำ

แทนที่จะใช้ <img> ผู้โจมตีอาจใช้ <script> เพื่อรวบรวมข้อมูลที่ละเอียดอ่อนไปยัง หน่วยความจำ:

<script src="https://your-bank.example/balance.json"></script>

Cross-Origin Read Block หรือ CORB เป็นฟีเจอร์ความปลอดภัยใหม่ที่ป้องกันเนื้อหาของ balance.json ไม่ให้ป้อนหน่วยความจำของโหมดแสดงภาพจะประมวลผลหน่วยความจำตามประเภท MIME

มาดูรายละเอียดวิธีการทำงานของ CORB เว็บไซต์สามารถขอทรัพยากรได้ 2 ประเภทจากเซิร์ฟเวอร์ ดังนี้

  1. ทรัพยากรข้อมูล เช่น เอกสาร HTML, XML หรือ JSON
  2. ทรัพยากรสื่อ เช่น รูปภาพ, 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 ที่มี ประเภท JSON MIME และทรัพยากร XML ที่มี ประเภท XML MIME)
  • เลือกไม่ใช้การดักจับโดยใช้ส่วนหัว 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 ในเฟรมทั้งหมดเชื่อถือได้มาก

อย่างไรก็ตาม แม้จะไม่มีการแยกเว็บไซต์ การไปยังส่วนต่างๆ ในเฟรมหลักบางส่วนจะเป็นแบบข้ามกระบวนการ ซึ่งส่งผล ยกเลิกการโหลดลักษณะการทำงานของตัวแฮนเดิล เช่น ถ้าคุณนำทางจาก old.example ไป new.example ด้วยการพิมพ์ URL ในแถบที่อยู่ การนำทางของ new.example จะเกิดขึ้นในกระบวนการใหม่ ยกเลิกการโหลด ตัวแฮนเดิลสำหรับ old.example และเฟรมย่อยทำงานในกระบวนการ old.example ในเบื้องหลัง หลังจากที่หน้า new.example แสดง และตัวจัดการสำหรับยกเลิกการโหลดแบบเก่าจะถูกยุติการใช้งานหากไม่ดำเนินการ เสร็จสิ้นภายในระยะเวลาที่กำหนด เนื่องจากตัวแฮนเดิลการยกเลิกการโหลดอาจไม่เสร็จก่อนหมดเวลา วิธีการยกเลิกการโหลดมีความน่าเชื่อถือน้อยลง

เมื่อใช้การแยกเว็บไซต์ การนำทางข้ามเว็บไซต์ทั้งหมดจะกลายเป็นแบบข้ามกระบวนการ ดังนั้นเอกสารจาก แต่ละไซต์ไม่ได้แชร์กระบวนการระหว่างกัน สถานการณ์ข้างต้นจึงมีผลกับ เคสอื่นๆ และตัวแฮนเดิลยกเลิกการโหลดใน <iframe> มักมีลักษณะการทำงานในเบื้องหลังและหมดเวลา ที่อธิบายไว้ข้างต้น

ข้อแตกต่างอีกประการหนึ่งที่เกิดจากการแยกเว็บไซต์คือการจัดลำดับพร้อมกันใหม่ของเครื่องจัดการยกเลิกการโหลด หากไม่มีการแยกเว็บไซต์ ตัวแฮนเดิลการยกเลิกการโหลดจะทำงานตามลำดับจากบนลงล่างที่เข้มงวดในเฟรม แต่ใช้ Site การแยกและตัวแฮนเดิลยกเลิกการโหลดจะทำงานพร้อมกันในกระบวนการต่างๆ

สิ่งเหล่านี้เป็นผลลัพธ์พื้นฐานของการเปิดใช้การแยกเว็บไซต์ ทีม 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 และ โธมัส สไตเนอร์ ที่อ่านบทความนี้ในฉบับร่างและแสดงความคิดเห็น