ساختن تجربهای غنی در وب امروزی تقریباً بهطور اجتنابناپذیری شامل جاسازی مؤلفهها و محتوایی است که کنترل واقعی روی آنها ندارید. ابزارههای شخص ثالث میتوانند تعامل را افزایش دهند و نقش مهمی در تجربه کلی کاربر ایفا کنند، و محتوای تولیدشده توسط کاربر گاهی حتی از محتوای بومی سایت مهمتر است. اجتناب از هرکدام واقعاً گزینه مناسبی نیست، اما هر دو خطر وقوع «اتفاق بد» را در سایتتان افزایش میدهند. هر ابزارکی که جاسازی میکنید (هر آگهی، هر ابزارک رسانههای اجتماعی) یک بُردار حمله بالقوه برای افرادی با نیت مخرب است:
خطمشی امنیتی محتوا (CSP) میتواند با دادن این امکان به شما که منابع مورداعتماد خاصی را برای اسکریپت و سایر محتواها در فهرست سفید قرار دهید، خطرات مرتبط با هر دو نوع محتوا را کاهش دهد. این گام بزرگی در مسیر درست است، اما شایان ذکر است که محافظتی که اکثر دستورالعملهای CSP ارائه میدهند دودویی است: منبع مجاز است یا نیست. مواقعی وجود دارد که گفتن این جمله مفید است: «مطمئن نیستم که واقعاً به این منبع محتوا اعتماد دارم، اما خیلی زیباست! لطفاً آن را جاسازی کن، مرورگر، اما نگذار سایتم را خراب کند.»
کمترین امتیاز
در اصل، ما بهدنبال سازوکاری هستیم که به ما اجازه دهد به محتوایی که جاسازی میکنیم فقط حداقل سطح قابلیت لازم برای انجام کارش را اعطا کنیم. اگر ابزارهای نیاز به باز کردن پنجره بالاپر جدیدی ندارد، سلب دسترسی به window.open ضرری ندارد. اگر به Flash نیاز ندارد، خاموش کردن پشتیبانی افزایه نباید مشکلی ایجاد کند. اگر از اصل حداقلسازی امتیاز پیروی کنیم و هر ویژگیای را که مستقیماً به عملکردی که میخواهیم استفاده کنیم مربوط نیست مسدود کنیم، تا جایی که ممکن است ایمن خواهیم بود. نتیجه این است که دیگر لازم نیست کورکورانه اعتماد کنیم که برخیاز محتواهای جاسازیشده از امتیازاتی که نباید استفاده کنند سوءاستفاده نخواهند کرد. بهسادگی از همان ابتدا به عملکرد دسترسی نخواهد داشت.
عناصر iframe اولین گام بهسوی چارچوبی خوب برای چنین راهحلی است.
بار کردن برخیاز مؤلفههای غیرقابلاعتماد در iframe، معیاری برای جداسازی
بین برنامه شما و محتوایی که میخواهید بار کنید ارائه میدهد. محتوای قاببندیشده به DOM صفحه شما یا دادههایی که بهصورت محلی ذخیره کردهاید دسترسی نخواهد داشت و نمیتواند در موقعیتهای دلخواه در صفحه ترسیم شود؛ دامنه آن به طرح کلی قاب محدود است. بااینحال، این جداسازی واقعاً قوی نیست. صفحه موجود
هنوز گزینههای متعددی برای رفتار آزاردهنده یا مخرب دارد: پخش خودکار
ویدیو، افزایهها، و بالاپرها فقط نوک کوه یخ هستند.
مشخصه sandbox از iframe عنصر
دقیقاً همان چیزی را که برای سختتر کردن محدودیتهای محتوای قاببندیشده نیاز داریم به ما میدهد. میتوانیم به مرورگر دستور دهیم محتوای قاب خاصی را در محیطی با امتیاز پایین بار کند و فقط به زیرمجموعهای از قابلیتهای لازم برای انجام هر کاری که باید انجام شود اجازه دهد.
اعتماد کن، اما درستیسنجی کن
دکمه «توییت» Twitter نمونهای عالی از عملکردی است که میتواند بهطور ایمنتر ازطریق جعبه امن در سایت شما جاسازی شود. Twitter به شما اجازه میدهد دکمه را ازطریق iframe با کد زیر جاسازی کنید:
<iframe src="https://platform.twitter.com/widgets/tweet_button.html"
style="border: 0; width:130px; height:20px;"></iframe>
برای اینکه بفهمیم چه چیزی را میتوانیم قفل کنیم، بیایید بهدقت بررسی کنیم که دکمه به چه قابلیتهایی نیاز دارد. HTML بارشده در قاب، کمی جاوا اسکریپت از سرورهای Twitter اجرا میکند و با کلیک کردن، بالاپر پرشده با میانای توییت کردن تولید میکند. آن میانای کاربری برای مرتبط کردن توییت به حساب صحیح، به دسترسی به کوکیهای Twitter نیاز دارد و باید بتواند فرم توییت کردن را ارسال کند. تقریباً همین است؛ قاب نیازی به بار کردن افزونه ندارد، نیازی به پیمایش پنجره سطح بالا ندارد، یا هر تعداد دیگری از عملکردها. چون به این امتیازها نیاز ندارد، بیایید آنها را با قرار دادن محتوای قاب در محیط امن حذف کنیم.
جعبه امن براساس فهرست مجاز کار میکند. ابتدا همه اجازههای ممکن را برمیداریم و سپس با افزودن پرچمهای خاص به پیکربندی جعبه امن، قابلیتهای فردی را دوباره روشن میکنیم. برای ابزاره Twitter، تصمیم گرفتهایم جاوا اسکریپت، بالاپرها، ارسال فرم، و کوکیهای twitter.com را فعال کنیم. میتوانیم این کار را با افزودن مشخصه sandbox به iframe با مقدار زیر انجام دهیم:
<iframe sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
src="https://platform.twitter.com/widgets/tweet_button.html"
style="border: 0; width:130px; height:20px;"></iframe>
همین! ما همه قابلیتهای موردنیاز را به قاب دادهایم و مرورگر بهطور مفید دسترسی به هریک از امتیازاتی را که صریحاً ازطریق مقدار مشخصه sandbox به آن اعطا نکردهایم، رد خواهد کرد.
کنترل جزئی بر قابلیتها
در مثال بالا چند پرچم جعبه امن احتمالی را دیدیم، اکنون بیایید عملکرد داخلی این مشخصه را با جزئیات بیشتری بررسی کنیم.
اگر iframe دارای مشخصه جعبه امن خالی باشد، سند قاببندیشده کاملاً در جعبه امن قرار میگیرد و مشمول محدودیتهای زیر میشود:
- جاوا اسکریپت در سند قاببندیشده اجرا نخواهد شد. این نه تنها شامل جاوا اسکریپت است که صریحاً ازطریق برچسبهای اسکریپت بارگیری میشود، بلکه شامل گردانندههای رویداد درونخطی و نشانیهای وب javascript: نیز میشود. این همچنین به این معنی است که محتوای موجود در برچسبهای noscript نمایش داده خواهد شد، دقیقاً مانند اینکه کاربر خودش اسکریپت را غیرفعال کرده باشد.
- سند قاببندیشده در مبدأ منحصربهفردی بار میشود، یعنی همه بررسیهای هممبدأ ناموفق خواهد بود؛ مبدأهای منحصربهفرد با هیچ مبدأ دیگری مطابقت ندارند، حتی با خودشان. ازجمله تأثیرات دیگر این است که سند به دادههای ذخیرهشده در کوکیهای هیچ مبدأیی یا هیچ سازوکار ذخیرهسازی دیگری (ذخیرهسازی DOM، Indexed DB، و غیره) دسترسی ندارد.
- سند قاببندیشده نمیتواند پنجره یا کادر گفتگوی جدیدی ایجاد کند (مثلاً ازطریق
window.openیاtarget="_blank"). - فرمها ارسال نمیشود.
- افزایهها بار نمیشوند.
- سند قاببندیشده فقط میتواند خودش را پیمایش کند، نه والد سطح بالای خود را.
تنظیم
window.top.locationاستثنایی ایجاد میکند و کلیک کردن روی پیوند باtarget="_top"تأثیری نخواهد داشت. - ویژگیهایی که بهطور خودکار راهاندازی میشوند (عناصر فرم با فوکوس خودکار، ویدیوهای با پخش خودکار، و غیره) مسدود میشوند.
- قفل اشارهگر را نمیتوان دریافت کرد.
- مشخصه
seamlessدرiframesکه سند قاببندیشده دارد نادیده گرفته میشود.
این کار بهخوبی سختگیرانه است و سندی که در iframe کاملاً جعبه امنی بار شده است
واقعاً خطر بسیار کمی دارد. البته، کار ارزشمند زیادی هم نمیتواند انجام دهد: شاید بتوانید برای برخی محتواهای ایستا از محیط ایمن کامل استفاده کنید، اما
در بیشتر مواقع میخواهید کمی محدودیتها را کاهش دهید.
بهاستثنای افزونهها، هرکدام از این محدودیتها را میتوان با افزودن پرچم به مقدار مشخصه sandbox برداشت. اسناد جعبه امن هرگز نمیتوانند افزونهها را اجرا کنند، زیرا افزونهها کد بومی بدون جعبه امن هستند، اما هر چیز دیگری مجاز است:
allow-formsارسال فرم را مجاز میکند.allow-popupsبه بالاپرها اجازه میدهد (شوک!).allow-pointer-lockقفل اشارهگر را مجاز میکند (جالب است!).allow-same-originبه سند اجازه میدهد مبدأ خود را حفظ کند؛ صفحههای بارشده ازhttps://example.com/دسترسی به دادههای آن مبدأ را حفظ خواهند کرد.allow-scriptsاجرای جاوا اسکریپت را مجاز میکند و همچنین به ویژگیها اجازه میدهد بهطور خودکار راهاندازی شوند (چون پیادهسازی آنها ازطریق جاوا اسکریپت بسیار ساده است).allow-top-navigationبه سند اجازه میدهد با پیمایش پنجره سطح بالا، از قاب خارج شود.
با درنظر گرفتن این موارد، میتوانیم دقیقاً ارزیابی کنیم که چرا در مثال Twitter بالا به مجموعه خاصی از پرچمهای سندباکس رسیدیم:
allow-scriptsلازم است، زیرا صفحهای که در قاب بار شده است، برای رسیدگی به تعامل کاربر، مقداری جاوا اسکریپت اجرا میکند.allow-popupsلازم است، زیرا دکمه فرم توییت کردن را در پنجره جدیدی باز میکند.allow-formsلازم است، زیرا فرم توییت کردن باید قابلارسال باشد.allow-same-originضروری است، زیرا درغیراینصورت کوکیهای twitter.com غیرقابلدسترس خواهند بود و کاربر نمیتواند برای پست کردن فرم به سیستم وارد شود.
نکته مهمی که باید به آن توجه داشت این است که پرچمهای جعبه امن اعمالشده بر یک قاب، بر هر پنجره یا قاب ایجادشده در جعبه امن نیز اعمال میشود. این یعنی باید allow-forms را به جعبه امن قاب اضافه کنیم، هرچند فرم فقط در پنجرهای که قاب بالا میآورد وجود دارد.
با وجود مشخصه sandbox، ابزاره فقط اجازههایی را که نیاز دارد دریافت میکند و قابلیتهایی مثل افزونهها، پیمایش بالا، و قفل اشارهگر مسدود میمانند. ما خطر جاسازی ابزاره را بدون هیچ اثر بدی کاهش دادهایم.
این کار به نفع همه افراد ذینفع است.
جداسازی امتیازات
جعبه شنی محتوای شخص ثالث برای اجرای کد غیرقابلاعتماد آنها در محیطی با امتیاز پایین بهطور واضحی مفید است. اما کد خودتان چطور؟ به خودت اعتماد داری، درست است؟ پس چرا باید نگران جعبه امن باشیم؟
من این سؤال را برعکس میکنم: اگر کد شما به افزونه نیاز ندارد، چرا به آن اجازه دسترسی به افزونهها را میدهید؟ در بهترین حالت، این امتیازی است که هرگز از آن استفاده نمیکنید، در بدترین حالت، یک بردار بالقوه برای مهاجمان است تا بتوانند به دستگاهتان نفوذ کنند. کد همه افراد دارای اشکال است و عملاً هر برنامهای به نحوی دربرابر سوءاستفاده آسیبپذیر است. قراردادن کد خودتان در جعبه شنی به این معنی است که حتی اگر مهاجم با موفقیت برنامه شما را خراب کند، دسترسی کامل به مبدأ برنامه به او داده نخواهد شد؛ او فقط میتواند کارهایی را انجام دهد که برنامه میتوانست انجام دهد. هنوز هم بد است، اما نه به بدی آنچه میتوانست باشد.
با تقسیم کردن برنامهتان به بخشهای منطقی و قرار دادن هر بخش در جعبه شنی با کمترین امتیاز ممکن، میتوانید این خطر را حتی بیشتر کاهش دهید. این تکنیک در کد بومی بسیار رایج است: برای مثال، Chrome خودش را به یک فرایند مرورگر با امتیاز بالا تقسیم میکند که به دیسک سخت محلی دسترسی دارد و میتواند اتصالات شبکه برقرار کند، و بسیاری از فرایندهای پردازنده با امتیاز پایین که کار سنگین تجزیه محتوای غیرقابلاعتماد را انجام میدهند. پردازنده نیازی به دسترسی به دیسک ندارد، مرورگر تمام اطلاعات لازم برای پردازش صفحه را در اختیار آن قرار میدهد. حتی اگر یک هکر باهوش راهی برای خراب کردن یک پردازنده پیدا کند، نمیتواند کار زیادی انجام دهد، زیرا پردازنده به تنهایی نمیتواند کار زیادی انجام دهد: تمام دسترسیهای با امتیاز بالا باید ازطریق فرایند مرورگر هدایت شوند. مهاجمان برای ایجاد هرگونه آسیب باید چندین حفره در بخشهای مختلف سیستم پیدا کنند، که این امر خطر نفوذ موفقیتآمیز را بهشدت کاهش میدهد.
درحال جعبه شنی کردن ایمن eval()
با جعبه امن و
postMessage API،
موفقیت این مدل بهراحتی در وب قابلاجرا است. بخشهایی از
برنامه شما میتواند در iframeهای جعبه امن قرار بگیرد و سند اصلی میتواند
با پست کردن پیام و گوش دادن به پاسخها، ارتباط بین آنها را
میانجیگری کند. این نوع ساختار تضمین میکند که سوءاستفاده در هر بخشی از برنامه حداقل آسیب ممکن را وارد کند. همچنین این مزیت را دارد که شما را مجبور میکند
نقاط ادغام واضحی ایجاد کنید، بنابراین دقیقاً میدانید کجا باید
مراقب اعتبارسنجی ورودی و خروجی باشید. بیایید یک مثال ساده را بررسی کنیم،
فقط برای اینکه ببینیم چگونه ممکن است کار کند.
Evalbox برنامه هیجانانگیزی است که رشتهای را میگیرد و آن را بهعنوان جاوا اسکریپت ارزیابی میکند. عالی است، نه؟ دقیقاً همان چیزی که در تمام این سالهای طولانی منتظرش بودهاید. البته این برنامه نسبتاً خطرناک است، زیرا اجازه دادن به اجرای جاوا اسکریپت دلخواه به این معنی است که هر دادهای که یک مبدأ ارائه میدهد قابل دسترسی است. ما خطر وقوع اتفاقات بد را با اطمینان از اینکه کد در داخل یک محیط امن اجرا میشود کاهش میدهیم، که این کار امنیت را تا حد زیادی افزایش میدهد. کد را از داخل به بیرون بررسی میکنیم و از محتوای قاب شروع میکنیم:
<!-- frame.html -->
<!DOCTYPE html>
<html>
<head>
<title>Evalbox's Frame</title>
<script>
window.addEventListener('message', function (e) {
var mainWindow = e.source;
var result = '';
try {
result = eval(e.data);
} catch (e) {
result = 'eval() threw an exception.';
}
mainWindow.postMessage(result, event.origin);
});
</script>
</head>
</html>
درون قاب، سند حداقلی داریم که بهسادگی با متصل شدن به رویداد message شیء window، به پیامهای والد خود گوش میدهد.
هرگاه والد postMessage را روی محتوای iframe اجرا کند، این رویداد
راهاندازی میشود و به ما امکان میدهد به رشتهای که والد میخواهد
اجرا کنیم دسترسی داشته باشیم.
در گرداننده، مشخصه source رویداد را که پنجره والد است میگیریم. از این اطلاعات برای ارسال نتیجه تلاشهایمان پساز اتمام کار استفاده خواهیم کرد. سپس ما کارهای سنگین را با انتقال دادههایی که به ما داده شده است به
eval() انجام خواهیم داد. This call has been wrapped up in a try block, as banned operations
inside a sandboxed iframe will frequently generate DOM exceptions; we'll catch
those and report a friendly error message instead. درنهایت، نتیجه را به پنجره اصلی برمیگردانیم. این موضوع بسیار ساده است.
والدین نیز به همین ترتیب ساده هستند. رابط کاربری کوچکی با textarea
برای کد و button برای اجرا ایجاد میکنیم و frame.html را ازطریق
iframe جعبه امنی که فقط اجرای اسکریپت را مجاز میکند وارد میکنیم:
<textarea id='code'></textarea>
<button id='safe'>eval() in a sandboxed frame.</button>
<iframe sandbox='allow-scripts'
id='sandboxed'
src='frame.html'></iframe>
اکنون موارد را برای اجرا آماده میکنیم. ابتدا به پاسخهای iframe گوش میدهیم و آنها را به کاربرانمان alert() میکنیم. احتمالاً یک برنامه واقعی
کار کمتر آزاردهندهای انجام میدهد:
window.addEventListener('message',
function (e) {
// Sandboxed iframes which lack the 'allow-same-origin'
// header have "null" rather than a valid origin. This means you still
// have to be careful about accepting data via the messaging API you
// create. Check that source, and validate those inputs!
var frame = document.getElementById('sandboxed');
if (e.origin === "null" && e.source === frame.contentWindow)
alert('Result: ' + e.data);
});
سپس، یک کنترلکننده رویداد را به کلیکها روی button متصل میکنیم. وقتی کاربر
کلیک میکند، محتوای فعلی textarea را میگیریم و آن را برای اجرا به
قاب منتقل میکنیم:
function evaluate() {
var frame = document.getElementById('sandboxed');
var code = document.getElementById('code').value;
// Note that we're sending the message to "*", rather than some specific
// origin. Sandboxed iframes which lack the 'allow-same-origin' header
// don't have an origin which you can target: you'll have to send to any
// origin, which might alow some esoteric attacks. Validate your output!
frame.contentWindow.postMessage(code, '*');
}
document.getElementById('safe').addEventListener('click', evaluate);
آسان است، نه؟ ما یک میانای برنامهسازی کاربردی ارزیابی بسیار ساده ایجاد کردهایم و میتوانیم مطمئن باشیم که کد ارزیابیشده به اطلاعات حساس مانند کوکیها یا فضای ذخیرهسازی DOM دسترسی ندارد. بههمینترتیب، کد ارزیابیشده نمیتواند افزونهها را بار کند، پنجرههای جدید باز کند، یا هر فعالیت آزاردهنده یا مخرب دیگری انجام دهد.
میتوانید همین کار را برای کد خودتان با تقسیم برنامههای یکپارچه به مؤلفههای تکمنظوره انجام دهید. هرکدام را میتوان در یک میانای برنامهسازی کاربردی پیامرسانی ساده قرار داد، درست مثل آنچه در بالا نوشتیم. پنجره والد با امتیاز بالا میتواند بهعنوان کنترلکننده و توزیعکننده عمل کند، پیامها را به ماژولهای خاصی ارسال کند که هرکدام کمترین امتیاز ممکن را برای انجام کارهای خود دارند، به نتایج گوش دهد و اطمینان حاصل کند که هر ماژول بهخوبی با اطلاعات موردنیاز خود تغذیه میشود.
اما توجه داشته باشید که هنگام سروکار داشتن با محتوای قاببندیشدهای که از همان مبدأ والد میآید، باید بسیار مراقب باشید. اگر صفحهای در
https://example.com/ صفحه دیگری را در همان مبدأ با جعبه امنی قاببندی کند
که شامل هر دو پرچم allow-same-origin و allow-scripts باشد،
صفحه قاببندیشده میتواند به والد دسترسی پیدا کند و مشخصه جعبه امن را
بهطور کامل حذف کند.
بازی کردن در محیط تمرینی
Sandboxing is available for you now in a variety of browsers: Firefox 17+,
IE10+, and Chrome at the time of writing (caniuse, of course, has an up-to-date
support table). با اعمال مشخصه sandbox به iframes که اضافه میکنید، میتوانید امتیازهای خاصی را به محتوایی که نمایش میدهند اعطا کنید، فقط امتیازهایی که برای عملکرد صحیح محتوا ضروری است. این کار به شما فرصت میدهد تا خطر مرتبط با گنجاندن محتوای شخص ثالث را فراتر از آنچه درحالحاضر با خطمشی امنیت محتوا امکانپذیر است کاهش دهید.
علاوهبراین، جعبه شنی تکنیک قدرتمندی برای کاهش خطر سوءاستفاده مهاجمان باهوش از حفرههای موجود در کد شما است. با جدا کردن یک برنامه یکپارچه به مجموعهای از سرویسهای محدودشده، که هرکدام مسئول بخش کوچکی از عملکرد مستقل هستند، مهاجمان مجبور خواهند شد نه تنها محتوای فریمهای خاص، بلکه کنترلکننده آنها را نیز به خطر بیندازند. این کار بسیار دشوارتری است، بهویژه ازآنجاییکه کنترلکننده میتواند بهطور قابلتوجهی در محدوده کاهش یابد. اگر از مرورگر بخواهید در بقیه موارد به شما کمک کند، میتوانید تلاشهای مربوط به امنیت را صرف ممیزی آن کد کنید.
این بدان معنا نیست که جعبه امن راهحل کاملی برای مشکل امنیت در اینترنت است. این روش دفاع در عمق را ارائه میدهد و تا زمانی که بر کلاینتهای کاربران خود کنترل نداشته باشید، نمیتوانید برای همه کاربران خود به پشتیبانی مرورگر تکیه کنید (اگر بر کلاینتهای کاربران خود کنترل دارید - مثلاً در یک محیط سازمانی - عالی است!). شاید روزی… اما درحالحاضر جعبه امن لایه دیگری از محافظت برای تقویت دفاع شما است، نه یک دفاع کامل که بتوانید بهتنهایی به آن تکیه کنید. بااینحال، لایهها عالی هستند. پیشنهاد میکنم از این یکی استفاده کنید.
مطالعه بیشتر
«جداسازی امتیاز در برنامههای HTML5» مقاله جالبی است که طراحی چارچوبی کوچک و کاربرد آن را در سه برنامه HTML5 موجود بررسی میکند.
ایجاد جعبه شنی میتواند حتی انعطافپذیرتر باشد وقتی با دو مشخصه iframe جدید دیگر ترکیب شود:
srcdoc، وseamless. مورد اول به شما امکان میدهد قاب را بدون سربار درخواست HTTP با محتوا پر کنید، و مورد دوم به سبک اجازه میدهد تا به محتوای قابشده جریان یابد. Both have fairly miserable browser support at the moment (Chrome and WebKit nightlies). but will be an interesting combination in the future. برای مثال، میتوانید نظرات مربوط به یک مقاله را ازطریق کد زیر در جعبه شنی قرار دهید:<iframe sandbox seamless srcdoc="<p>This is a user's comment! It can't execute script! Hooray for safety!</p>"></iframe>