ارتباط امن بین مبدأهای مختلف با PostMessage API را کشف کنید. با قابلیتها، خطرات امنیتی و بهترین شیوهها برای کاهش آسیبپذیریها در برنامههای وب آشنا شوید.
ارتباط بین مبدأهای مختلف: الگوهای امنیتی با PostMessage API
در وب مدرن، برنامهها اغلب نیاز به تعامل با منابعی از مبدأهای مختلف دارند. سیاست همان مبدأ (SOP) یک مکانیسم امنیتی حیاتی است که اسکریپتها را از دسترسی به منابعی از یک مبدأ متفاوت محدود میکند. با این حال، سناریوهای مشروعی وجود دارد که در آنها ارتباط بین مبدأهای مختلف ضروری است. API postMessage یک مکانیسم کنترلشده برای دستیابی به این هدف فراهم میکند، اما درک خطرات امنیتی بالقوه آن و پیادهسازی الگوهای امنیتی مناسب حیاتی است.
درک سیاست همان مبدأ (SOP)
سیاست همان مبدأ یک مفهوم امنیتی بنیادین در مرورگرهای وب است. این سیاست صفحات وب را از ارسال درخواست به دامنهای متفاوت از دامنهای که صفحه وب را ارائه کرده است، محدود میکند. یک مبدأ توسط طرح (پروتکل)، میزبان (دامنه) و پورت تعریف میشود. اگر هر یک از اینها متفاوت باشد، مبدأها متفاوت در نظر گرفته میشوند. برای مثال:
https://example.comhttps://www.example.comhttp://example.comhttps://example.com:8080
همه اینها مبدأهای متفاوتی هستند و SOP دسترسی مستقیم اسکریپت بین آنها را محدود میکند.
معرفی PostMessage API
API postMessage یک مکانیسم امن و کنترلشده برای ارتباط بین مبدأهای مختلف فراهم میکند. این API به اسکریپتها اجازه میدهد تا پیامهایی را به پنجرههای دیگر (مانند iframeها، پنجرههای جدید یا تبها) بدون توجه به مبدأ آنها ارسال کنند. پنجره گیرنده سپس میتواند به این پیامها گوش داده و آنها را بر اساس آن پردازش کند.
سینتکس پایه برای ارسال یک پیام به این صورت است:
otherWindow.postMessage(message, targetOrigin);
otherWindow: ارجاعی به پنجره هدف (مثلاًwindow.parent،iframe.contentWindowیا یک شیء پنجره که ازwindow.openبه دست آمده است).message: دادهای که میخواهید ارسال کنید. این میتواند هر شیء جاوا اسکریپتی باشد که قابل سریالسازی است (مانند رشتهها، اعداد، اشیاء، آرایهها).targetOrigin: مبدأیی را که میخواهید پیام را به آن ارسال کنید، مشخص میکند. این یک پارامتر امنیتی حیاتی است.
در سمت گیرنده، شما باید به رویداد message گوش دهید:
window.addEventListener('message', function(event) {
// ...
});
شیء event شامل ویژگیهای زیر است:
event.data: پیامی که توسط پنجره دیگر ارسال شده است.event.origin: مبدأ پنجرهای که پیام را ارسال کرده است.event.source: ارجاعی به پنجرهای که پیام را ارسال کرده است.
خطرات و آسیبپذیریهای امنیتی
در حالی که postMessage راهی برای دور زدن محدودیتهای SOP ارائه میدهد، اما اگر با دقت پیادهسازی نشود، خطرات امنیتی بالقوهای را نیز به همراه دارد. در اینجا برخی از آسیبپذیریهای رایج آورده شده است:
۱. عدم تطابق مبدأ هدف
عدم اعتبارسنجی ویژگی event.origin یک آسیبپذیری حیاتی است. اگر گیرنده به طور کورکورانه به پیام اعتماد کند، هر وبسایتی میتواند دادههای مخرب ارسال کند. همیشه قبل از پردازش پیام، تأیید کنید که event.origin با مبدأ مورد انتظار مطابقت دارد.
مثال (کد آسیبپذیر):
window.addEventListener('message', function(event) {
// این کار را انجام ندهید!
processMessage(event.data);
});
مثال (کد امن):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Received message from untrusted origin:', event.origin);
return;
}
processMessage(event.data);
});
۲. تزریق داده
رفتار با دادههای دریافتی (event.data) به عنوان کد اجرایی یا تزریق مستقیم آن به DOM میتواند منجر به آسیبپذیریهای Cross-Site Scripting (XSS) شود. همیشه دادههای دریافتی را قبل از استفاده، پاکسازی و اعتبارسنجی کنید.
مثال (کد آسیبپذیر):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
document.body.innerHTML = event.data; // این کار را انجام ندهید!
}
});
مثال (کد امن):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
const sanitizedData = sanitize(event.data); // یک تابع پاکسازی مناسب پیادهسازی کنید
document.getElementById('message-container').textContent = sanitizedData;
}
});
function sanitize(data) {
// منطق پاکسازی قوی را در اینجا پیادهسازی کنید.
// به عنوان مثال، از DOMPurify یا کتابخانهای مشابه استفاده کنید
return DOMPurify.sanitize(data);
}
۳. حملات مرد میانی (MITM)
اگر ارتباط از طریق یک کانال ناامن (HTTP) صورت گیرد، یک مهاجم MITM میتواند پیامها را رهگیری و تغییر دهد. همیشه از HTTPS برای ارتباط امن استفاده کنید.
۴. جعل درخواست بین سایتی (CSRF)
اگر گیرنده بر اساس پیام دریافتی بدون اعتبارسنجی مناسب اقداماتی انجام دهد، یک مهاجم میتواند پیامها را جعل کرده تا گیرنده را به انجام اقدامات ناخواسته فریب دهد. مکانیزمهای محافظت از CSRF را پیادهسازی کنید، مانند گنجاندن یک توکن مخفی در پیام و تأیید آن در سمت گیرنده.
۵. استفاده از Wildcard در targetOrigin
تنظیم targetOrigin به * به هر مبدأیی اجازه میدهد تا پیام را دریافت کند. از این کار باید اجتناب شود مگر اینکه کاملاً ضروری باشد، زیرا هدف امنیت مبتنی بر مبدأ را از بین میبرد. اگر مجبور به استفاده از * هستید، اطمینان حاصل کنید که اقدامات امنیتی قوی دیگری مانند کدهای احراز هویت پیام (MACs) را پیادهسازی کردهاید.
مثال (از این کار اجتناب کنید):
otherWindow.postMessage(message, '*'); // از استفاده از '*' خودداری کنید مگر اینکه کاملاً ضروری باشد
الگوهای امنیتی و بهترین شیوهها
برای کاهش خطرات مرتبط با postMessage، این الگوهای امنیتی و بهترین شیوهها را دنبال کنید:
۱. اعتبارسنجی دقیق مبدأ
همیشه ویژگی event.origin را در سمت گیرنده اعتبارسنجی کنید. آن را با یک لیست از پیش تعریفشده از مبدأهای مورد اعتماد مقایسه کنید. برای مقایسه از برابری دقیق (===) استفاده کنید.
۲. پاکسازی و اعتبارسنجی دادهها
تمام دادههای دریافتی از طریق postMessage را قبل از استفاده، پاکسازی و اعتبارسنجی کنید. بسته به نحوه استفاده از دادهها، از تکنیکهای پاکسازی مناسب استفاده کنید (مانند HTML escaping، URL encoding، اعتبارسنجی ورودی). از کتابخانههایی مانند DOMPurify برای پاکسازی HTML استفاده کنید.
۳. کدهای احراز هویت پیام (MACs)
یک کد احراز هویت پیام (MAC) را در پیام بگنجانید تا از یکپارچگی و اصالت آن اطمینان حاصل کنید. فرستنده MAC را با استفاده از یک کلید مخفی مشترک محاسبه کرده و آن را در پیام قرار میدهد. گیرنده MAC را با استفاده از همان کلید مخفی مشترک دوباره محاسبه کرده و آن را با MAC دریافتی مقایسه میکند. اگر مطابقت داشته باشند، پیام اصیل و دستکاری نشده در نظر گرفته میشود.
مثال (با استفاده از HMAC-SHA256):
// فرستنده
async function sendMessage(message, targetOrigin, sharedSecret) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: message,
signature: signatureHex
};
otherWindow.postMessage(securedMessage, targetOrigin);
}
// گیرنده
async function receiveMessage(event, sharedSecret) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Received message from untrusted origin:', event.origin);
return;
}
const securedMessage = event.data;
const message = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Message is authentic!');
processMessage(message); // پردازش پیام را ادامه دهید
} else {
console.error('Message signature verification failed!');
}
}
مهم: کلید مخفی مشترک باید به طور امن تولید و ذخیره شود. از هاردکد کردن کلید در کد خودداری کنید.
۴. استفاده از Nonce و Timestamp
برای جلوگیری از حملات بازپخش (replay attacks)، یک nonce (عدد یکبار مصرف) منحصر به فرد و یک timestamp در پیام قرار دهید. گیرنده سپس میتواند تأیید کند که nonce قبلاً استفاده نشده و timestamp در یک بازه زمانی قابل قبول است. این کار خطر بازپخش پیامهای رهگیری شده قبلی توسط یک مهاجم را کاهش میدهد.
۵. اصل کمترین امتیاز
فقط حداقل امتیازات لازم را به پنجره دیگر اعطا کنید. به عنوان مثال، اگر پنجره دیگر فقط نیاز به خواندن دادهها دارد، به آن اجازه نوشتن داده را ندهید. پروتکل ارتباطی خود را با در نظر گرفتن اصل کمترین امتیاز طراحی کنید.
۶. سیاست امنیت محتوا (CSP)
از سیاست امنیت محتوا (CSP) برای محدود کردن منابعی که اسکریپتها میتوانند از آنها بارگیری شوند و اقداماتی که اسکریپتها میتوانند انجام دهند، استفاده کنید. این میتواند به کاهش تأثیر آسیبپذیریهای XSS که ممکن است از مدیریت نادرست دادههای postMessage ناشی شوند، کمک کند.
۷. اعتبارسنجی ورودی
همیشه ساختار و فرمت دادههای دریافتی را اعتبارسنجی کنید. یک فرمت پیام واضح تعریف کنید و اطمینان حاصل کنید که دادههای دریافتی با این فرمت مطابقت دارند. این به جلوگیری از رفتار غیرمنتظره و آسیبپذیریها کمک میکند.
۸. سریالسازی امن دادهها
از یک فرمت سریالسازی امن داده مانند JSON برای سریالسازی و دیسریالسازی پیامها استفاده کنید. از فرمتهایی که اجازه اجرای کد را میدهند، مانند eval() یا Function()، خودداری کنید.
۹. محدود کردن اندازه پیام
اندازه پیامهای ارسالی از طریق postMessage را محدود کنید. پیامهای بزرگ میتوانند منابع بیش از حدی را مصرف کرده و به طور بالقوه منجر به حملات انکار سرویس (denial-of-service) شوند.
۱۰. بازرسیهای امنیتی منظم
بازرسیهای امنیتی منظمی از کد خود انجام دهید تا آسیبپذیریهای بالقوه را شناسایی و برطرف کنید. به پیادهسازی postMessage توجه ویژهای داشته باشید و اطمینان حاصل کنید که تمام بهترین شیوههای امنیتی رعایت شدهاند.
سناریوی نمونه: ارتباط امن بین یک Iframe و والد آن
سناریویی را در نظر بگیرید که در آن یک iframe میزبانی شده در https://iframe.example.com نیاز به ارتباط با صفحه والد خود در https://parent.example.com دارد. iframe باید دادههای کاربر را برای پردازش به صفحه والد ارسال کند.
Iframe (https://iframe.example.com):
// یک کلید مخفی مشترک ایجاد کنید (با یک روش امن تولید کلید جایگزین کنید)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
// دریافت دادههای کاربر
const userData = {
name: 'John Doe',
email: 'john.doe@example.com'
};
// ارسال دادههای کاربر به صفحه والد
async function sendUserData(userData) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: userData,
signature: signatureHex
};
parent.postMessage(securedMessage, 'https://parent.example.com');
}
sendUserData(userData);
صفحه والد (https://parent.example.com):
// کلید مخفی مشترک (باید با کلید iframe مطابقت داشته باشد)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
window.addEventListener('message', async function(event) {
if (event.origin !== 'https://iframe.example.com') {
console.warn('Received message from untrusted origin:', event.origin);
return;
}
const securedMessage = event.data;
const userData = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Message is authentic!');
// پردازش دادههای کاربر
console.log('User data:', userData);
} else {
console.error('Message signature verification failed!');
}
});
نکات مهم:
YOUR_SECURE_SHARED_SECRETرا با یک کلید مخفی مشترک که به صورت امن تولید شده است، جایگزین کنید.- کلید مخفی مشترک باید در هر دو، iframe و صفحه والد، یکسان باشد.
- این مثال از HMAC-SHA256 برای احراز هویت پیام استفاده میکند.
نتیجهگیری
API postMessage ابزار قدرتمندی برای فعال کردن ارتباط بین مبدأهای مختلف در برنامههای وب است. با این حال، درک خطرات امنیتی بالقوه و پیادهسازی الگوهای امنیتی مناسب برای کاهش این خطرات بسیار مهم است. با پیروی از الگوهای امنیتی و بهترین شیوههای ذکر شده در این راهنما، میتوانید به طور امن از postMessage برای ساخت برنامههای وب قوی و امن استفاده کنید.
به یاد داشته باشید که همیشه امنیت را در اولویت قرار دهید و با آخرین بهترین شیوههای امنیتی برای توسعه وب بهروز بمانید. به طور منظم کد و تنظیمات امنیتی خود را بازبینی کنید تا اطمینان حاصل کنید که برنامههای شما در برابر آسیبپذیریهای بالقوه محافظت میشوند.