ระบบส่วนขยายของ Chrome บังคับใช้นโยบายรักษาความปลอดภัยเนื้อหา (CSP) ที่ค่อนข้างเ��้��������
ข้������������ของนโยบายนั้นไม่ซับซ้อน: ต้องย้ายสคริปต์ออกนอกบรรทัดไปยังไฟล์ JavaScript แยกต่างหาก ต้องแปลงเครื่องจัดการเหตุการณ์ในบรรทัดไปใช้ addEventListener
และปิดใช้ eval()
แอป Chrome มีนโยบายที่เข้มงวดมากยิ่งขึ้น และเราค่อนข้างพอใจกับคุณสมบัติความปลอดภัยที่นโยบายเหล่านี้มีให้
อย่างไรก็ตาม เราทราบว่าไลบรารีมากมายใช้โครงสร้าง eval()
และ eval
เช่น new Function()
เพื่อเพิ่มประสิทธิภาพการทำงานและความสะดวกในการแสดงออก ไลบรารีเทมพลิเมนต์
มีแนวโน้มใช้งานรูปแบบนี้เป็นพิเศษ แม้ว่าบางส่วน (เช่น Angular.js) จะรองรับ CSP แบบสำเร็จรูป แต่เฟรมเวิร์กยอดนิยมจำนวนมากยังไม่ได้อัปเดตเป็นกลไกที่เข้ากันได้กับโลกที่ไม่มีeval
ของส่วนขยาย การเลิกรองรับฟังก์ชันดังกล่าวจึงได้รับการพิสูจน์แล้วว่าเป็นปัญหามากกว่าที่คาดไว้สำหรับนักพัฒนาซอฟต์แวร์
เอกสารนี้จะแนะนำแซนด์บ็อกซ์เป็นกลไกที่ปลอดภัยในการรวมไลบรารีเหล่านี้ไว้ในโปรเจ็กต์ของคุณโดยไม่กระทบต่อความปลอดภัย โดยเราจะใช้คำว่าส่วนขยายทั้งหมดเพื่อความสั้นกระชับ แต่แนวคิดนี้ก็ใช้กับแอปพลิเคชันได้ไม่แพ้��ัน
ทำไมต้องใช้แซนด์บ็อกซ์
eval
เป็นอันตรายภายในส่วนขยายเนื่องจากโค้ดที่ใช้มีสิทธิ์เข้าถึงทุกอย่างในสภาพแวดล้อมที่มีสิทธิ์สูงของส่วนขยาย chrome.*
API ที่มีประสิทธิภาพจำนวนมากพร้อมให้บริการที่อาจส่งผลกระทบต่อความปลอดภัยและความเป็นส่วนตัวของผู้ใช้อย่างมาก สิ่งที่เรากังวลน้อยที่สุดคือการขโมยข้อมูลแบบง่ายๆ
โซลูชันที่นำเสนอคือแซนด์บ็อกซ์ที่ eval
สามารถเรียกใช้โค้ดได้โดยไม่ต้องเข้าถึงข้อมูลของส่วนขยายหรือ API ที่มีมูลค่าสูงของส่วนขยาย ไม่มีข้อมูล ไม่มี API ก็ไม่มีปัญหา
ด้วยการแสดงไฟล์ HTML ที่ต้องการภายในแพ็กเกจส่วนขยายว่าเป็นแบบแซนด์บ็อกซ์
เมื่อใดก็ตามที่โหลดหน้าเว็บที่มีแซนด์บ็อกซ์ ระบบจะย้ายหน้าดังกล่าวไปยังต้นทางที่ไม่ซ้ำกันและจะเข้าถึง API ของ chrome.*
ไม่ได้ หากเราโหลดหน้าแซนด์บ็อกซ์นี้ไปยังส่วนขยายของเราผ่านทาง iframe
เราจะส่งข้อความ ให้หน้าเว็บดำเนินการกับข้อความเหล่านั้นในทางใดทางหนึ่ง และรอให้หน้าเว็บส่งผลลัพธ์กลับมา กลไกการส่งข้อความที่เรียบง่ายนี้ทำให้เรามีทุกอย่างที่จำเป็นเพื่อใส่โค้ดที่ขับเคลื่อนโดย eval
ไว้ในเวิร์กโฟลว์ของส่วนขยายอย่างปลอดภัย
การสร้างและการใช้แซนด์บ็อกซ์
หากต้องการเจาะลึกเรื่องโค้ดโดยตรง โปรดนำส่วนขยายตัวอย่างแซนด์บ็อกซ์และนำออก ตัวอย่างนี้ใช้ได้ผลดีของ API การรับส่งข้อความขนาดเล็กซึ่งสร้างต่อยอดจากไลบรารีเทมเพลตแฮนเดิล ซึ่งน่าจะแสดงทุกอย่างที่จำเป็นต่อการดำเนินการต่อ สำหรับคนที่อยากท��าบคำอธิบายเพิ่มเติม เรามาดูตัวอย่างกันที่นี่
แสดงรายการไฟล์ในไฟล์ Manifest
แต่ละไฟล์ที่ควรเรียกใช้ภายในแซนด์บ็อกซ์ต้องแสดงอยู่ในไฟล์ Manifest ของส่วนขยายด้วยการเพิ่มพร็อพเพอร์ตี้ sandbox
นี่เป็นขั้นตอนสำคัญและอาจลืมได้ง่ายๆ ดังนั้นโปรดตรวจสอบอีกครั้งว่ามีไฟล์แซนด์บ็อกซ์ของคุณอยู่ในไฟล์ Manifest ในตัวอย่างนี้ เรากำลังทำแซนด์บ็อกซ์กับไฟล์ที่ชื่อ "sandbox.html" อย่างชาญฉลาด รายการไฟล์ Manifest จะมีลักษณะดังนี้
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
โหลดไฟล์ที่ทำแซนด์บ็อกซ์
ในการทำสิ่งที่น่าสนใจกับไฟล์แซนด์บ็อกซ์ เราต้องโหลดไฟล์ในบริบทที่โค้ดของส่วนขยายจัดการได้ ที่นี่มีการโหลด sandbox.html ลงในหน้ากิจกรรม (eventpage.html) ของส่วนขยายผ่าน iframe
eventpage.js มีโค้ดที่ส่งข้อความไปยังแซนด์บ็อกซ์เมื่อมีการคลิกการดำเนินการของเบราว์เซอร์โดยการค้นหา iframe
ในหน้าเว็บและเรียกใช้เมธอด postMessage
ใน contentWindow
ข้อความนี้เป็นออบเจ็กต์ที่มีพร็อพเพอร์ตี้ 2 รายการ ได้แก่ context
และ command
เราจะเจาะลึกทั้งสองอย่างในอีกสักครู่
chrome.browserAction.onClicked.addListener(function() {
var iframe = document.getElementById('theFrame');
var message = {
command: 'render',
context: {thing: 'world'}
};
iframe.contentWindow.postMessage(message, '*');
});
postMessage
API ได้ที่เอกสารประกอบเกี่ยวกับ postMessage
เกี่ยวกับ MDN ซึ่งค่อนข้างครบถ้วนและควรค่าแก่การอ่าน กล่าวอย่างเจาะจงคือ โปรดทราบว่าข้อมูลจะสามารถส่งผ่านข้��มูลไปกลับมาได้เฉพาะเมื่อทำให้เป็นอนุกรมได้ เช่น ฟังก์ชันจะไม่ใช่ทำสิ่งที่เป็นอันตราย
เมื่อโหลด sandbox.html
เครื่องมือจะโหลดไลบรารีของ Handlebar รวมถึงสร้างและ����ม���������เ��������ลตอินไลน์ในลักษณะที่แฮนเดิลแนะนำ
<script src="handlebars-1.0.0.beta.6.js"></script>
<script id="hello-world-template" type="text/x-handlebars-template">
<div class="entry">
<h1>Hello, !</h1>
</div>
</script>
<script>
var templates = [];
var source = document.getElementById('hello-world-template').innerHTML;
templates['hello'] = Handlebars.compile(source);
</script>
ซึ่งก็ไม่ได้ล้มเหลว! ถึงแม้ว่า Handlebars.compile
จะลงเอยด้วยการใช้ new Function
แต่สิ่งต่างๆ ก็ทำงาน
ตามที่คาดไว้ทุกประการ เราจึงใช้เทมเพลตที่คอมไ���ล์ใน templates['hello']
ส่งคืนผลลัพธ์
เราจะทำให้เทมเพลตนี้ใช้งานได้โดยการตั้งค่า Listener ข้อความที่ยอมรับคำสั่งจากหน้ากิจกรรม เราจะใช้ command
ที่ส่งมาเพื่อตัดสินว่าควรทำอย่างไร (คุณอาจทำได้มากกว่าแค่การแสดงผล หรืออาจจะสร้างเทมเพลตก็ได้) บางทีการจัดการครีเอทีฟโฆษณาเหล่านี้ไม่แน่) และจะมีการส่ง context
ไปยังเทมเพลตโดยตรงเพื่อการแสดงผล HTML ที่แสดงผลจะถูกส่งกลับไปยังหน้ากิจกรรม เพื่อให้ส่วนขยายสามารถทำสิ่งที่มีประโยชน์ได้ในภายหลัง
<script>
window.addEventListener('message', function(event) {
var command = event.data.command;
var name = event.data.name || 'hello';
switch(command) {
case 'render':
event.source.postMessage({
name: name,
html: templates[name](event.data.context)
}, event.origin);
break;
// case 'somethingElse':
// ...
}
});
</script>
กลับไปที่หน้ากิจกรรม เราจะได้รับข้อความนี้และดำเนินการที่น่าสนใจกับhtml
ข้อมูลที่เราได้รับ ในกรณีนี้ เราจะแสดงการแจ้งเตือนผ่านการแจ้งเตือนบนเด��ก์ท็อป แต่คุณสามารถใช้ HTML นี้เป็นส่วนหนึ่งของ UI ของส่วนขยายได้อย่างปลอดภัย การแทรกโค้ดผ่าน innerHTML
ไม่ก่อให้เกิดความเสี่ยงด้านความปลอดภัยที่สำคัญ เนื่องจากแม้แต่การบุกรุกโค้ดที่แซนด์บ็อกซ์ด้วยการโจมตีที่ชาญฉลาดก็อาจไม่สามารถแทรกสคริปต์หรือปลั๊กอินที่เป็นอันตรายลงในบริบทของส่วนขยายที่มีสิทธิ์สูงได้
กลไกนี้ทำให้การกำหนดเทมเพลตเป็นเรื่องง่าย แต่ไม่ได้จำกัดเพียงเทมเพลต คุณอาจแซนด์บ็อกซ์โค้ดใดๆ ที่ทำงานได้ไม่ดีภายใต้นโยบายรักษาความปลอดภัยเนื้อหาที่เข้มงวด อันที่จริงมักมีประโยชน์สำหรับคอมโพเนนต์แซนด์บ็อกซ์ของส่วนขยายที่จะทำงานได้อย่างถูกต้องเพื่อจำกัดแต่ละส่วนของโปรแกรมให้เหลือน้อยที่สุดเท่าที่จำเป็นต่อการดำเนินการอย่างถูกต้อง งานนำเสนอ การเขียนเว็บแอปที่ปลอดภัยและส่วนขยาย Chrome จาก Google I/O 2012 เป็นตัวอย่างที่ดีของการใช้งานเทคนิคเหล่านี้ และใช้เวลา 56 นาที